- 投稿日:2019-01-26T22:29:38+09:00
特定の条件を満たす要素を指定個数分だけ取り出す関数の実装
はじめに
以前の 記事 に書いた「特定の条件を満たす要素を指定個数分だけ取り出す」に必要な実験が全て完了したので、実際の動作を実装する。
なお、中で使用している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の標準構文やシンタックスシュガーで置き換えられるものを見つけたら随時対応する。
- 投稿日:2019-01-26T22:09:37+09:00
【Swift4.2】Realmで連番のプライマリキーを含むデータを登録してTableViewで表示するサンプル
登録したいデータの一覧
SampleDataModel.swiftimport 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.swiftimport 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.swiftimport 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.swiftimport 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
- 投稿日:2019-01-26T20:54:47+09:00
MultiPeerConnectivityのライブラリBeerKitを作りました。
ARKitを使った空間共有アプリを作るに当たってデバイス間通信の実装についてよく考えています。
iPhoneどうしで通信するに当たって一番便利なのがMultiPeerConnectivityです。MultiPeerConnectivityはWifiやbluetooth、そしてiPhoneの持っているp2p通信のシステムを使ってローカルのデバイス間通信をするためのフレームワーク。
そしてこのMultiPeerConnectivityを上からラップして使いやすくしているライブラリにPeerKitがあります。
日本語記事だと以下で紹介されています。
[iOS][Swift] イベント駆動型で P2P 通信をするPeerKit
PeerKitに問題があったのでフォークした
しかし、このPeerKit、
- 繋がりにくい
- 一度外れると復活できない
などなどのバグが少しあり、そのまま使うとハマってしまいます。メンテされていると良いのですが、このライブラリは現在メンテされていないようです。
ということで僕がフォークしていくつか修正したのがkboy-silvergym/PeerKitです。
しかしまだ問題があって、うまく繋がらないことがありました。
もう上から編集していくのはきつい。。!ってことで、真似して新しいライブラリをつくってみました。
BeerKit
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もお待ちしています。
あと、⭐️もお待ちしてます。
- 投稿日:2019-01-26T20:19:24+09:00
[RxSwift] flatMapの呼び出しを恐がりたくない人生だった
flatMap
の呼び出しが理解不能だったプログラマの記録です。(ビギナー向け)
この記事だけでflatMap
の挙動が把握できるようにはならないのですが、
私のような悩める人に対し、違った角度からの見方を提供できたならば僥倖です...。環境
Xcode 10.1(10B61)
Swift 4.2.1
RxSwift 4.4.0flatMapの呼び出しが理解不能だった
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
の話が食い込んでくることの???感といったらありませんでした。この記事を書くことで、自らに巣食っているこの恐怖を少しでも克服していきたいと思います。
恐さを克服する
まず、
map
もflatMap
もオペレーターの一種であることに変わりはなく、
「新しいObservableを返す」という点で目的は共通していることがわかると、いくばくか落ち着きを取り戻せます。
私が大好きないつものやつ「関数のシグネチャを確認」をここで早くも投入してみましょう。
mapの定義とflatMapの定義を確認してみます。mappublic func map<R>(_ transform: @escaping (E) throws -> R) -> Observable<O.E>flatMappublic func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O) -> Observable<O.E>
map
とflatMap
双方、返り値はObservable<O.E>
で共通しています。
flatMap
といえど、RxSwiftの数多あるオペレータの一種であることに変わりはないことが、いまいちど確認できました。そして、
flatMap
が引数として取るクロージャが遵守すべき型の約束はどうなっているでしょうか?flatMap(再掲)public func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O) -> Observable<O.E>
- 型パラメータとその型制約
<O: ObservableConvertibleType>
と、- 引数としてのクロージャ(selector)の返り値の型
O
に着目ください。
この2点を総合すると、「クロージャ(selector)は返り値としてObservable(ConvertibleType)を返さないとだめだよ!」という指定がなされていることがわかります。つまり、このようなコードは書けません。
let result: Observable<String> = Observable.of("梅", "竹", "松") .flatMap { text -> String in // 返り値はString(= Observable(ConvertibleType)ではない)から?♀️ return "\(text)☆" }クロージャの内部に書かれる処理が得てして複雑になるのも、
Observable(ConvertibleType)
を作って返さないといけないのだから、ある意味当然だったというわけですね...。この際ですので、ついでに
map
が引数として取るクロージャのシグネチャも見てみましょう。mappublic 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
を使っても書けるのでは?書けます。結果の型がネストしてもいいのであれば。
冒頭の例において、
flatMap
をmap
に書き換えてみると...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()
Observable
がObservable
でネストしてしまう結果になってしまいました...。この挙動は、
↓のように、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アプリ設計パターン入門
- 投稿日:2019-01-26T15:56:28+09:00
テストコードとログの記事掲載の仕方について
はじめに
過去の 記事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
- 投稿日:2019-01-26T05:31:10+09:00
Swift4でライフゲームを楽しむ
ちょっと見てくれ!!!
初めてSwiftでアプリを作りました。
まだ洗練されていないですが、皆さんのアドヴァイスが頂きたく投稿します。
Swift4
xcode10.1
iPad第6世代
iPhone6s
で動作確認済み今後の課題
Intervalの処理
より効率的に処理(今は3万cellでもう重い)
一時停止やcellの数を変化させるボタンをつける
長生きしているcellに色の変化をつけるのも面白そうViewController.swiftimport 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.swiftimport 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 } }