20190126のSwiftに関する記事は6件です。

特定の条件を満たす要素を指定個数分だけ取り出す関数の実装

はじめに

以前の 記事 に書いた「特定の条件を満たす要素を指定個数分だけ取り出す」に必要な実験が全て完了したので、実際の動作を実装する。

なお、中で使用しているgetElements()の実装は こちら

実装

ソースコード
func filterElements(point: Int, elements: Int, array: [Int]) -> [Int] {
    let targetList = array.filter{ point <= $0 }
    return getElements(elements: elements, array: targetList)
}
テストパターン
(point: 0, elements:5, array: [1,3,5,7,9]),[1,3,5,7,9],"全取出(対象:0以上の要素)")
(point: 5, elements:0, array: [1,3,5,7,9]),[],         "最大3個 取出個数 下限値未満")
(point: 5, elements:1, array: [1,3,5,7,9]),[5],        "最大3個 取出個数 下限値")
(point: 5, elements:3, array: [1,3,5,7,9]),[5,7,9],    "最大3個 取出個数 上限値")
(point: 5, elements:4, array: [1,3,5,7,9]),[5,7,9],    "最大3個 取出個数 上限値超過")
(point:10, elements:5, array: [1,3,5,7,9]),[],         "該当なし(対象:10以上の要素)")
実行結果
0 failures

テストパターンと実行結果の表記についての記事

結果

予定通りの動作が完成した。
テストコードもあるので、Swiftの標準構文やシンタックスシュガーで置き換えられるものを見つけたら随時対応する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift4.2】Realmで連番のプライマリキーを含むデータを登録してTableViewで表示するサンプル

登録したいデータの一覧

SampleDataModel.swift
import RealmSwift

class SampleDataModel: Object {
    @objc dynamic var id = 0
    @objc dynamic var name = ""
    @objc dynamic var memo: String?
    @objc dynamic var category = 0

    override static func primaryKey() -> String? {
        return "id"
    }
}

enum SampleDataCategory: Int {
    case life
    case work
}

連番のプライマリキーが必要な場合に適合するプロトコル

RealmPrimaryKeyIncrementerProtocol.swift
import RealmSwift

protocol RealmPrimaryKeyIncrementerProtocol {
    func newId<T: Object>(model: T) -> Int
}

extension RealmPrimaryKeyIncrementerProtocol {

    func newId<T: Object>(model: T) -> Int {
        guard let key = T.primaryKey() else { fatalError("このオブジェクトにはプライマリキーがありません") }

        // Realmのインスタンスを取得
        let realm = try! Realm()
        // 最後のプライマリーキーを取得
        if let last = realm.objects(T.self).sorted(byKeyPath: "id", ascending: true).last,
            let lastId = last[key] as? Int {
            return lastId + 1 // 最後のプライマリキーに+1した数値を返す
        } else {
            return 0  // 初めて使う際は0を返す
        }
    }
}

データ登録画面(後述のTableVCからModalで遷移する)

RecordVC.swift
import UIKit
import RealmSwift

class RecordVC: UIViewController, RealmPrimaryKeyIncrementerProtocol {

    // MARK: - Properties
    @IBOutlet weak var categorySegment: UISegmentedControl!
    @IBOutlet weak var nameField: UITextField!
    @IBOutlet weak var memoField: UITextField!
    @IBOutlet weak var recordButton: UIButton!

    // MARK: - Actions
    @IBAction func recordSampleData(_ sender: UIButton) {

        let realm = try! Realm()

        let sampleData = createSampleData()

        try! realm.write {
            realm.add(sampleData)
        }
        print("Recorded:", sampleData)

        dismiss(animated: true, completion: nil)
    }

    @IBAction func cancelAndReturn(_ sender: UIBarButtonItem) {
        dismiss(animated: true, completion: nil)
    }

    // MARK: - Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        recordButton.isEnabled = false
    }

    // MARK: - Private methods
    private func validateSampleInput() -> Bool {

        guard let nameText = nameField.text else { return false }
        let isValid = nameText.count > 2

        return isValid
    }

    private func createSampleData() -> SampleDataModel {
        let sampleData = SampleDataModel()

        sampleData.id = newId(model: sampleData)
        sampleData.name = nameField.text
        sampleData.memo = memoField.text
        sampleData.category = categorySegment.selectedSegmentIndex

        return sampleData
    }
}

// MARK: - UITextFieldDelegate
extension RecordVC: UITextFieldDelegate {
    func textFieldDidEndEditing(_ textField: UITextField) {
        recordButton.isEnabled = validateSampleInput()
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.endEditing(true)
        return true
    }

    func textFieldShouldClear(_ textField: UITextField) -> Bool {
        if textField == nameField {
            recordButton.isEnabled = false
        }
        return true
    }
}

登録したデータをTableViewで表示する

DisplayTableVC.swift
import UIKit
import RealmSwift

class DisplayTableVC: UITableViewController {

    var result: Results<SampleDataModel>?

    override func viewDidLoad() {
        super.viewDidLoad()

        let realm = try! Realm()

        result = realm.objects(SampleDataModel.self)

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        tableView.reloadData()
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return result?.count ?? 0
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "sampleDataCell", for: indexPath)

        guard let sampleDate = result?[indexPath.row] else { return cell }

        cell.textLabel?.text = sampleDate.name
        cell.detailTextLabel?.text = sampleDate.memo

        // カテゴリーによって背景色を変える
        switch SampleDataCategory(rawValue: sampleDate.category)! {
        case .life:
            cell.backgroundColor = #colorLiteral(red: 1, green: 0.5781051517, blue: 0, alpha: 1)
        case .work:
            cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
        }

        return cell
    }

全データはGitHubに上げてあります。
https://github.com/Riscait/RealmSwiftSample

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MultiPeerConnectivityのライブラリBeerKitを作りました。

ARKitを使った空間共有アプリを作るに当たってデバイス間通信の実装についてよく考えています。

iPhoneどうしで通信するに当たって一番便利なのがMultiPeerConnectivityです。MultiPeerConnectivityはWifiやbluetooth、そしてiPhoneの持っているp2p通信のシステムを使ってローカルのデバイス間通信をするためのフレームワーク

そしてこのMultiPeerConnectivityを上からラップして使いやすくしているライブラリにPeerKitがあります。

日本語記事だと以下で紹介されています。

[iOS][Swift] イベント駆動型で P2P 通信をするPeerKit

PeerKitに問題があったのでフォークした

しかし、このPeerKit、

  • 繋がりにくい
  • 一度外れると復活できない

などなどのバグが少しあり、そのまま使うとハマってしまいます。メンテされていると良いのですが、このライブラリは現在メンテされていないようです。

ということで僕がフォークしていくつか修正したのがkboy-silvergym/PeerKitです。

しかしまだ問題があって、うまく繋がらないことがありました。

もう上から編集していくのはきつい。。!ってことで、真似して新しいライブラリをつくってみました。

BeerKit

スクリーンショット 2019-01-26 20.52.24.png

PeerKitを真似てBeerKitという名前で作ってみました。ビールで乾杯するというローカルコミュニケーションはまさにP2P通信と言っても過言ではありません。

PeerKitとの大きな違いはData型を渡す前提で作っているところです。

データ送るとき

let data: Data = try! JSONEncoder().encode(message)
BeerKit.sendEvent("message", data: data)

PeerKitはAny型を渡すように作られているのですが、これが少し罠で、中でNSKeyedArchiverでアーカイブされてDataになるので、NSCodingに準拠している型じゃないとダメで、準拠してない型を渡すとクラッシュします。

だったら最初からData型を渡すようにしようということで、作り直しました。

データ受け取るとき

BeerKit.onEvent { (peerId, event, data) in
    guard let data = data,
        let message = try? JSONDecoder().decode(MessageEntity.self, from: data) else {
            return
    }
    self.messages.append(message)

    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

JSONにして送受信することを想定してデモアプリはつくられています。

まとめ

ということで、もし良ければBeeKit使ってみてくださいー!何かあればPRもお待ちしています。

あと、⭐️もお待ちしてます。

https://github.com/kboy-silvergym/BeerKit

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[RxSwift] flatMapの呼び出しを恐がりたくない人生だった

flatMapの呼び出しが理解不能だったプログラマの記録です。(ビギナー向け)
この記事だけでflatMapの挙動が把握できるようにはならないのですが、
私のような悩める人に対し、違った角度からの見方を提供できたならば僥倖です...。

環境

Xcode 10.1(10B61)
Swift 4.2.1
RxSwift 4.4.0

flatMapの呼び出しが理解不能だった

mapのほうはまだやってることわかるんですよ。
ただ、flatMapとなると...あいつなんなの?
ただでさえオペレータのメソッドチェーンを追ってるだけでも脳メモリに負荷がかかっているのに、
引数のクロージャの中で、なぜいきなり仰々しい構文が途中で入ってくるのか...こわい...

let result: Observable<Bool> = userNameTextField.rx.text
    .orEmpty
    .asObservable()
    .skip(1)
    .debounce(0.3, scheduler:  MainScheduler.instance)
    .distinctUntilChanged()                            // ここまで1つのObservableの話をしていたと思っていたのに...
    .flatMap { userName in
        return userNameValidator.validate(text: text)  // なんかいきなり別のObservableの話が登場してきてこわい
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn(false)
    }
    .share()

1つのObservableの話をしていたと思いきや、
新しいObservableの話が食い込んでくることの???感といったらありませんでした。

この記事を書くことで、自らに巣食っているこの恐怖を少しでも克服していきたいと思います。

恐さを克服する

まず、mapflatMapもオペレーターの一種であることに変わりはなく、
「新しいObservableを返す」という点で目的は共通していることがわかると、いくばくか落ち着きを取り戻せます。
私が大好きないつものやつ「関数のシグネチャを確認」をここで早くも投入してみましょう。
mapの定義flatMapの定義を確認してみます。

map
public func map<R>(_ transform: @escaping (E) throws -> R) -> Observable<O.E>
flatMap
public func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O) -> Observable<O.E>

mapflatMap双方、返り値はObservable<O.E>で共通しています。
flatMapといえど、RxSwiftの数多あるオペレータの一種であることに変わりはないことが、いまいちど確認できました。

そして、flatMapが引数として取るクロージャが遵守すべき型の約束はどうなっているでしょうか?

flatMap(再掲)
public func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O) -> Observable<O.E>
  1. 型パラメータとその型制約<O: ObservableConvertibleType>
    と、
  2. 引数としてのクロージャ(selector)の返り値の型O

に着目ください。
この2点を総合すると、「クロージャ(selector)は返り値としてObservable(ConvertibleType)を返さないとだめだよ!」という指定がなされていることがわかります。

つまり、このようなコードは書けません。

let result: Observable<String> = Observable.of("梅", "竹", "松")
    .flatMap { text -> String in  // 返り値はString(= Observable(ConvertibleType)ではない)から?‍♀️
        return "\(text)☆"
    }

クロージャの内部に書かれる処理が得てして複雑になるのも、
Observable(ConvertibleType)を作って返さないといけないのだから、ある意味当然だったというわけですね...。

この際ですので、ついでにmapが引数として取るクロージャのシグネチャも見てみましょう。

map
public func map<R>(_ transform: @escaping (E) throws -> R) -> Observable<O.E>

引数としてのクロージャ(transform)の返り値の型Rを見て分かる通り、
Observable(ConvertibleType)を返してね!」というflatMapのときとは対照的に、クロージャの返り値の型に制約は設けられていないことが確認できます。
先ほどのflatMapの例では書けなかったコードも、mapであれば当然のようにOKです。

let result: Observable<String> = Observable.of("梅", "竹", "松")
    .map { text -> String in  // ?‍♀️
        return "\(text)☆"
    }

Observableをreturnする形で書けると何が嬉しいのか

自身や、自身が保有するモデルクラスが持つObservableを返すメソッドを呼び出すことで、
flatMapが引数に取るクロージャの要件を満たすことができます。
冒頭の例を取るなら...

こんなバリデーションを担うクラスがあったとして...
struct UserNameValidator {
    func validate(userName: String) -> Observable<Bool> {
        if ... /* なんらかのバリデーションロジック */ {
            return .just(false)  // バリデーション失敗
        } else {
            return .just(true)   // バリデーション成功
        }
    }
}
flatMapのクロージャ内で使用する
let validator = UserNameValidator()

userNameTextField.rx.text
    .orEmpty
    .asObservable()
    .skip(1)
    .debounce(0.3, scheduler:  MainScheduler.instance)
    .distinctUntilChanged()
    .flatMap { userName in
        return validator.validate(text: text)  // 自身が保有するモデルクラスのメソッド呼び出し
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn(false)
    }
    .share()                                   // もちろん後続でチェーンもできる

バリデーションを担うクラスのメソッドを呼び出す形でflatMapが引数として取るクロージャをしっかり充足させ、flatMapを呼んています。
こうした形を取ることで、責務を適切に分割した設計に近づけやすくなる可能性などが考えられます。

逆に言えば、モデルクラスはObservableを返すようにメソッドを定義してあげると、
その利用者側は後続のオペレータにチェーンしてもらえたりなど扱いやすくなる、といったメリットが考えられます。
(iOSアプリ設計パターン入門 第7章 MVVM〜Modelの項に記載されていました。)

筆者が知覚しているメリットはこれだけですが、他にもたくさんありそうです。

同じことはmapを使っても書けるのでは?

書けます。結果の型がネストしてもいいのであれば

冒頭の例において、flatMapmapに書き換えてみると...

let result: Observable<Observable<Bool>> = usernameTextField.rx.text  // 型がネストしてしまった?
    .orEmpty
    .asObservable()
    .skip(1)
    .debounce(0.3, scheduler:  MainScheduler.instance)
    .distinctUntilChanged()
    .map { [unowned self] text in  // なぜならここをflatMapからmapに変えてしまったからね...
        return self.validate(text: text)
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn(false)
    }
    .share()

ObservableObservableでネストしてしまう結果になってしまいました...。

この挙動は、
↓のように、Stringを返すクロージャを取るmapの結果 => Observable<String> になる、のだから...

let result: Observable<String> = Observable.of("梅", "竹", "松")
    .map { text -> String in
        return "\(text)☆"
    }

↓のように、Observable<String>を返すクロージャを取るmapの結果 => Observable<Observable<String> になる、というわけですね。

let result: Observable<Observable<String>> = Observable.of("梅", "竹", "松")
    .map { text -> Observable<String> in
        return Observable.just("\(text)☆")
    }

そしてこのネストされたObservableを購読したいとなると、目もあてられない感じになってしまいます。

こんなコード書きたいと思う人はいない...??
result.subscribe(onNext: { innerObservable in
    innerObservable.subscribe(onNext: { text in
        print(text)
    })
    .disposed(by: self.bag)
})
.disposed(by: bag)

flatMapを使えば、Observableでネストすることなく結果を返してくれます。

let result: Observable<String> = Observable.of("梅", "竹", "松")
    .flatMap { text -> Observable<String> in
        return Observable.just("\(text)☆")
    }

もっとflatMapと仲良くなりたいな

この記事では一切触れなかった切り口である

元の Observable のイベントを Observable に変換した上で、その発行するイベントをマージします。

という点に言及しているこちらなどを参考にさせてもらっています。
他にもこちらにあるような、

flatMapやflatMapLatestはクロージャ内で生成されたオブザーバブルにCompletedが流れても、それをまとめた出力にはCompletedが流れない

という点も見逃せないですね。
これを知ってからマーブル図を見直すと、確かにflatMapのクロージャに書かれているonCompleted(縦線)が、合成後のストリーム上では流れて(書かれて)いないことに気づけました...。

おわりに

初めて筆者がRxSwiftに触れてからすでに1年半を数えますが、
flatMapだけが、というよりRxSwift自体、全然自信が湧かないのです。難しいよ...?
どうかこの記事が私のような悩める人に対し、違った角度からの見方を少しでも提供できていたら...と願っています?

参考書籍

・モバイルアプリ開発エキスパート養成読本
・RxSwift研究読本 Ⅰ
・iOSアプリ設計パターン入門

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テストコードとログの記事掲載の仕方について

はじめに

過去の 記事1 記事2 で、テストコードとログについての記事上の可読性を検証してきた。
本記事ではその内容を統合し、今後の記事の「記載内容の読み方」リンクを受けるページとして機能することを目的する。

原文まま

実コード
func getElements(elements: Int, array: [Int]) -> [Int] {
    let cnt = round(target: elements, min: 0, max: array.count)
    return [ Int ](array[0..<cnt])
}
テストコード
class FuncTests : XCTestCase {
    func testGetElements() {
        XCTAssertEqual( getElements(elements: -1, array: [1,3,5,7,9]), [],          "取出個数 下限値未満")
        XCTAssertEqual( getElements(elements:  0, array: [1,3,5,7,9]), [],          "取出個数 下限値未満")
        XCTAssertEqual( getElements(elements:  1, array: [1,3,5,7,9]), [1],         "取出個数 下限値")
        XCTAssertEqual( getElements(elements:  5, array: [1,3,5,7,9]), [1,3,5,7,9], "取出個数 上限値")
        XCTAssertEqual( getElements(elements:  6, array: [1,3,5,7,9]), [1,3,5,7,9], "取出個数 上限値超過")
        XCTAssertEqual( getElements(elements: -1, array: []), [],                   "取出個数 下限値未満")
        XCTAssertEqual( getElements(elements:  0, array: []), [],                   "取出個数 下限値未満")
        XCTAssertEqual( getElements(elements:  1, array: []), [],                   "取出個数 下限値")
        XCTAssertEqual( getElements(elements:  5, array: []), [],                   "取出個数 上限値")
        XCTAssertEqual( getElements(elements:  6, array: []), [],                   "取出個数 上限値超過")
    }
}
実行結果
Test Suite 'FuncTests' started at 2019-01-26 15:06:41.432
Test Case '-[__lldb_expr_27.FuncTests testGetElements]' started.
Test Case '-[__lldb_expr_27.FuncTests testGetElements]' passed (0.090 seconds).
Test Case '-[__lldb_expr_27.FuncTests testRound]' started.
Test Case '-[__lldb_expr_27.FuncTests testRound]' passed (0.001 seconds).
Test Suite 'FuncTests' passed at 2019-01-26 15:06:41.577.
     Executed 2 tests, with 0 failures (0 unexpected) in 0.090 (0.145) seconds

可読性向上

実コード
func getElements(elements: Int, array: [Int]) -> [Int] {
    let cnt = round(target: elements, min: 0, max: array.count)
    return [ Int ](array[0..<cnt])
}
getElementsのテストコード(XCTAssertEqual比較)
(elements: -1, array: [1,3,5,7,9]), [],          "取出個数 下限値未満"
(elements:  0, array: [1,3,5,7,9]), [],          "取出個数 下限値未満"
(elements:  1, array: [1,3,5,7,9]), [1],         "取出個数 下限値"
(elements:  5, array: [1,3,5,7,9]), [1,3,5,7,9], "取出個数 上限値"
(elements:  6, array: [1,3,5,7,9]), [1,3,5,7,9], "取出個数 上限値超過"
(elements: -1, array: []), [],                   "取出個数 下限値未満"
(elements:  0, array: []), [],                   "取出個数 下限値未満"
(elements:  1, array: []), [],                   "取出個数 下限値"
(elements:  5, array: []), [],                   "取出個数 上限値"
(elements:  6, array: []), [],                   "取出個数 上限値超過"
実行結果
0 failures
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift4でライフゲームを楽しむ

ちょっと見てくれ!!!

初めてSwiftでアプリを作りました。
まだ洗練されていないですが、皆さんのアドヴァイスが頂きたく投稿します。
Swift4
xcode10.1
iPad第6世代
iPhone6s
で動作確認済み

今後の課題

Intervalの処理
より効率的に処理(今は3万cellでもう重い)
一時停止やcellの数を変化させるボタンをつける
長生きしているcellに色の変化をつけるのも面白そう

ViewController.swift
import UIKit
import SpriteKit
class ViewController: UIViewController {
    //方形作成関数
    func fillRect(recSize:CGSize,color:CGColor) -> UIImage {
        //↓引数:図形の大きさ、”不透明さ”、よくわからん
        UIGraphicsBeginImageContextWithOptions(recSize, false, 0.0)
        let context = UIGraphicsGetCurrentContext()
        let origin = CGPoint(x:0, y: 0)
        let rect = CGRect(origin: origin, size: recSize)

        //pathとは何か?
        let path = UIBezierPath(rect: rect)
        context?.setFillColor(color)

        path.fill()

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image!
    }

    override func viewDidLoad() {
        //方形のサイズ
        let widthOfARect    =   UIScreen.main.bounds.width/CGFloat(width)
        let heightOfARect   =   UIScreen.main.bounds.height/CGFloat(height)
        let size:CGSize = CGSize(width: widthOfARect, height: heightOfARect)
        var rectangle:UIImage!

        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.view.backgroundColor = UIColor.black
        //cellの配列を作成
        makeCellsStructure()
        //celsBackUpも同構造にする
        cellsBackUp=cells
     //方形のimageを作る
         rectangle = self.fillRect(recSize:size, color: UIColor.green.cgColor)

        //空の配列を作成
        var rectView = [UIImageView?]()
        for _ in 0...width*height-1{
            rectView.append(nil)
        }

        //初期設定用ランダム
        let startNum:Int = Int.random(in:0...height*width-1)
        //テスト用初期設定どんぐり
            cells[startNum].dOA=1
            cells[startNum+width+2].dOA=1
            cells[startNum+width*2-1].dOA=1
            cells[startNum+width*2].dOA=1
            cells[startNum+width*2+3].dOA=1
            cells[startNum+width*2+4].dOA=1
            cells[startNum+width*2+5].dOA=1
        //テスト用銀河
       for i in 0...5{
            cells[coorToCellNum(x: 0+width/2, y: i+height/2)].dOA=1
            cells[coorToCellNum(x: 1+width/2, y: i+height/2)].dOA=1

            cells[coorToCellNum(x: 7+width/2, y: i+3+height/2)].dOA=1
            cells[coorToCellNum(x: 8+width/2, y: i+3+height/2)].dOA=1

            cells[coorToCellNum(x: i+3+width/2, y: 0+height/2)].dOA=1
            cells[coorToCellNum(x: i+3+width/2, y: 1+height/2)].dOA=1

            cells[coorToCellNum(x: i+width/2, y: 7+height/2)].dOA=1
            cells[coorToCellNum(x: i+width/2, y: 8+height/2)].dOA=1
        }
        //テスト用パルサー
        for i in 0...2{
            //横並びの
            cells[coorToCellNum(x: 15+2+i, y: 20+6)].dOA=1
            cells[coorToCellNum(x: 15-2-i, y: 20+6)].dOA=1

            cells[coorToCellNum(x: 15+2+i, y: 20+1)].dOA=1
            cells[coorToCellNum(x: 15-2-i, y: 20+1)].dOA=1

            cells[coorToCellNum(x: 15+2+i, y: 20-1)].dOA=1
            cells[coorToCellNum(x: 15-2-i, y: 20-1)].dOA=1

            cells[coorToCellNum(x: 15+2+i, y: 20-6)].dOA=1
            cells[coorToCellNum(x: 15-2-i, y: 20-6)].dOA=1
            //縦並びの
            cells[coorToCellNum(x: 15-6, y: 20+2+i)].dOA=1
            cells[coorToCellNum(x: 15-6, y: 20-2-i)].dOA=1

            cells[coorToCellNum(x: 15-1, y: 20+2+i)].dOA=1
            cells[coorToCellNum(x: 15-1, y: 20-2-i)].dOA=1

            cells[coorToCellNum(x: 15+1, y: 20+2+i)].dOA=1
            cells[coorToCellNum(x: 15+1, y: 20-2-i)].dOA=1

            cells[coorToCellNum(x: 15+6, y: 20+2+i)].dOA=1
            cells[coorToCellNum(x: 15+6, y: 20-2-i)].dOA=1
        }

        //計算と描写をする。
        //この時間処理をもっと簡単にできないか?
        _=Timer.scheduledTimer(withTimeInterval: speed*2.0, repeats: true) { (timer) in
            rectView = self.drawCells(rectangle: rectangle, rectViewTan: rectView, widthOfARect: widthOfARect)
            DispatchQueue.main.asyncAfter(deadline: .now()+speed, execute: {
                rectView = self.drawCellsBackUp(rectangle: rectangle, rectViewTan: rectView, widthOfARect: widthOfARect)}
        )}
    }

    func drawCells(rectangle:UIImage,rectViewTan:[UIImageView?],widthOfARect:CGFloat) ->[UIImageView?] {
        var rectView = rectViewTan

        for i in 0...width*height-1{
            //前回の描写の削除
            if rectView[i] != nil {
                rectView[i]!.removeFromSuperview()
            }
            //描写開始
            if cells[i].dOA==1 {
                //そこにimageを追加。この時点でrectView[i]はnilじゃない
                rectView[i] = UIImageView(image: rectangle)
                //rectViewのアンカーポイントを左上にする
                rectView[i]!.layer.anchorPoint = CGPoint(x: 0.0, y: 0.0)
                //ancherポイントのsuperview座標x,y
                let xOfRect = CGFloat(cellNumToCoorX(celNum: i))*widthOfARect
                let yOfRect = CGFloat(cellNumToCoorY(celNum: i))*widthOfARect
                rectView[i]!.center =  CGPoint(x: xOfRect, y: yOfRect)
                self.view.addSubview(rectView[i]!)

            }
            //cellsから次のcellsBackUpを決めておく
            gameOfLife1(i: i)
        }
        return rectView
    }
    func drawCellsBackUp(rectangle:UIImage,rectViewTan:[UIImageView?],widthOfARect:CGFloat)->[UIImageView?] {
        var rectView = rectViewTan
        //cellsbackupより描写
        for m in 0...width*height-1{
            //さっきの描写の削除
            if rectView[m] != nil {
                rectView[m]!.removeFromSuperview()
            }
            if cellsBackUp[m].dOA==1{
                rectView[m] = UIImageView(image:rectangle)
                rectView[m]!.layer.anchorPoint = CGPoint(x: 0.0, y: 0.0)
                //ancherポイントのsuperview座標x,y
                let xOfRect = CGFloat(cellNumToCoorX(celNum: m))*widthOfARect
                let yOfRect = CGFloat(cellNumToCoorY(celNum: m))*widthOfARect
                rectView[m]!.center =  CGPoint(x: xOfRect, y: yOfRect)
                self.view.addSubview(rectView[m]!)
            }//cellsVackUpから次のcellsを用意
            gameOfLife2(i: m)
        }
        return rectView
    }
       }

Cell.swift
import Foundation

//下処理
//描写と描写のインターバル(秒)
let speed:Double   = 0.05

//並べるcellの数...height:width = 4:3にしよう(iPadのアスペクト比は4:3)
let height  = 200
let width   = 150
struct Cell{
    var x:Int
    var y:Int
    //DeadOrAlive 0:死亡,1:生存
    var dOA:Int
}
//cellsとcellsBackUpを交互に計算し、描写する
//他のもっとシンプルな方法はないか?
var cells = [Cell]()
var cellsBackUp = [Cell]()

/*
 配列の並べ方、地球の歩き方
 example
 0  1   2   3
 4  5   6   7
 8  9   10  11
 */
//cellの配列を作成
func makeCellsStructure() {
    for i in 0...height-1 {
        for m in 0...width-1{
            cells.append(Cell(x: m, y: i, dOA: 0))
        }
    }
}
//座標系と配列の接続
//ドラクエ式ループX
func modifyX(v:Int ) ->Int {
    var modifiedX = v
    if v<0 {
        modifiedX = v+width
    }else if v>width-1{
        modifiedX = v-width
    }
    return modifiedX
}
//ドラクエ式ループY
func modifyY(v:Int ) ->Int {
    var modifiedY = v
    if v<0 {
        modifiedY = v+height
    }else if v>height-1{
        modifiedY = v-height
    }
    return modifiedY
}
//座標からcells[]のindex番号へ
func coorToCellNum(x:Int,y:Int)-> Int {
    var celNum = 0
    let xx = modifyX(v: x)
    let yy = modifyY(v: y)
    celNum = width*yy+xx
    return celNum
}
//index番号からx座標を出す
func cellNumToCoorX(celNum:Int)->Int {
    return celNum%width
}
//index番号からy座標を出す
func cellNumToCoorY(celNum:Int)->Int {
    var retY = 0
    for i in 0...height{
        if celNum+1 <= i*width {
            retY = i-1
            break
        }}
    return retY
}

//cellsでの周囲のセルの生死を確認
func countAliveNeighbors(celNum:Int)->Int {

    let coorX = cellNumToCoorX(celNum: celNum)
    let coorY = cellNumToCoorY(celNum: celNum)


    let uL = coorToCellNum(x: coorX-1, y: coorY+1)
    let uC = coorToCellNum(x: coorX, y: coorY+1)
    let uR = coorToCellNum(x: coorX+1, y: coorY+1)

    let mL = coorToCellNum(x: coorX-1, y: coorY)
    let mR = coorToCellNum(x: coorX+1, y: coorY)

    let lL = coorToCellNum(x: coorX-1, y: coorY-1)
    let lC = coorToCellNum(x: coorX, y: coorY-1)
    let lR = coorToCellNum(x: coorX+1, y: coorY-1)

    let counter =   cells[uL].dOA+cells[uC].dOA+cells[uR].dOA +
        cells[mL].dOA+cells[mR].dOA +
        cells[lL].dOA+cells[lC].dOA+cells[lR].dOA
    return counter
}
//cellsBackUpでの周囲のセルの生死を確認
func countAliveNeighborsAmongCellBackUp(celNum:Int)->Int {

    let coorX = cellNumToCoorX(celNum: celNum)
    let coorY = cellNumToCoorY(celNum: celNum)


    let uL = coorToCellNum(x: coorX-1, y: coorY+1)
    let uC = coorToCellNum(x: coorX, y: coorY+1)
    let uR = coorToCellNum(x: coorX+1, y: coorY+1)

    let mL = coorToCellNum(x: coorX-1, y: coorY)
    let mR = coorToCellNum(x: coorX+1, y: coorY)

    let lL = coorToCellNum(x: coorX-1, y: coorY-1)
    let lC = coorToCellNum(x: coorX, y: coorY-1)
    let lR = coorToCellNum(x: coorX+1, y: coorY-1)

    let counter =   cellsBackUp[uL].dOA+cellsBackUp[uC].dOA+cellsBackUp[uR].dOA +
        cellsBackUp[mL].dOA+cellsBackUp[mR].dOA +
        cellsBackUp[lL].dOA+cellsBackUp[lC].dOA+cellsBackUp[lR].dOA
    return counter
}

func gameOfLife1(i:Int) {
    //cellsでの周囲を考えてcellsBackUpに反映する関数
        let lives = countAliveNeighbors(celNum: i)
        switch lives {
            //中央のcellに接するcellのうち3個が生存なら中央のcellは誕生(あるいは生存)
        case 3:
            cellsBackUp[i].dOA=1
            //中央のcellに接するcellのうち2個が生存なら中央のcellは現状維持(死亡状態なら死亡、生存状態なら生存)
        case 2:
            cellsBackUp[i].dOA=cells[i].dOA
            //過密、過疎で死亡
        case 0,1,4,5,6,7,8:
            cellsBackUp[i].dOA=0
            if cells[i].dOA==1{
                    }
            //とりあえず0代入
        default:
            cellsBackUp[i].dOA=0
                }
    }

func gameOfLife2(i:Int) {
    //cellsBackUpでの周囲を考えてcellに反映する関数
        let lives = countAliveNeighborsAmongCellBackUp(celNum: i)
        switch lives {
        case 3:
            cells[i].dOA=1
        case 2:
            cells[i].dOA=cellsBackUp[i].dOA

        case 0,1,4,5,6,7,8:
            cells[i].dOA=0
            if cellsBackUp[i].dOA==1{
            }
        default:
            cells[i].dOA=0
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む