見出し画像

【Swift】RxSwiftでデータ配列の変化を監視してMVCパターンでUITableViewを描画してみる【RxSwift】

前回書いたMVCパターンの記事はこちらです。

今回気をつけたこと

①UITableViewを表示に使っている配列の変化を監視して、
変更後が加えられた時に自動でUITableViewを更新する。

②また、Observerパターン、Delegeteパターンではなく、
RxSwiftでデータ監視を行い、実行する。

上記の点に気をつけて実装しました。

アプリ実装イメージ

かなりシンプルに作りました。
初期表示で3件セルが表示されていて、セルをタップすると画像がどんどん増えます。
セルの描画とタップイベントを取得しにいく実装を行う必要性があります。

画像1


Modelで行っていること

・UITableViewで表示に使っている、BehaviorRelay<[TweetModel]>を保持しておく。

import Foundation
import RxSwift
import RxCocoa

class TweetListModel {
   var tweetList: [TweetModel] = [
       TweetModel.init(initTweet:"Tweet: 0番目"),
       TweetModel.init(initTweet:"Tweet: 1番目"),
       TweetModel.init(initTweet:"Tweet: 2番目")
   ]
   // TableViewの表示データ 配列として使用している
   var relayItems: BehaviorRelay<[TweetModel]>?
   
   init() {
       relayItems = BehaviorRelay<[TweetModel]>(value: tweetList)
   }

}

Viewで行っていること

前回から変化なしです。

import UIKit
import RxCocoa
import RxSwift

class TweetListView: UIView {
   @IBOutlet weak var tableView: UITableView!
   
   override init(frame: CGRect){
       super.init(frame: frame)
       loadNib()
   }

   required init(coder aDecoder: NSCoder) {
       super.init(coder: aDecoder)!
       loadNib()
   }

   func loadNib(){
       let view = Bundle.main.loadNibNamed("TweetListView", owner: self, options: nil)?.first as! UIView
       view.frame = self.bounds
       self.addSubview(view)
   }

}

Controllerで行っていること

・ データの変更が行われた時にセルの描画を行う。
・セルをタップするとデータを追加する。

解説していきます。

・ データの変更が行われた時にセルの描画を行う。
Modelのデータの変更をControllerに通知し、ControllerからViewに対して、画面を更新する処理を実行する。

・セルをタップするとデータを追加する。
Viewでユーザーアクションを受け、Controllerに通知し、Modelのデータを変更する処理を実行する。

import UIKit
import RxSwift
import RxCocoa

class TweetViewController: UIViewController {
   
   var tweetListView : TweetListView?
   var tweetListModel = TweetListModel()
   let disposeBag = DisposeBag()

   override func viewDidLoad() {
       super.viewDidLoad()
       
       // StoryboardでのTweetViewControllerの親ViewがTweetListViewなので取得できる。
       guard let view = view as? TweetListView else { return }
       tweetListView = view
       
       // Modelのデータをbindingする為に一度呼び出しておく。
       bindRelayItems()
   }
   

   func bindRelayItems() {
       guard let items = tweetListModel.relayItems else { return }
       guard let tableView = tweetListView?.tableView else { return }
       
       // TableViewに表示するCellを登録する
       tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

       // ModelのデータrelayItemsの値が更新された時にUITableViewセルを描画する
       // ModelのデータrelayItemsの値が更新された時に呼び出される。
       items.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { row, element, cell in
           let tweetModel : TweetModel = items.value[row]
           cell.textLabel?.text = tweetModel.tweet
       }
       .disposed(by: disposeBag)
       
       // tableViewのセルをタップした時のメソッド
       tableView.rx.itemSelected
           .subscribe(onNext: { indexPath in
               let newTweetModel = TweetModel(initTweet: "Tweet:\(items.value.count)番目")
               let newValue = items.value + [newTweetModel]
               // Modelのデータを上書きする(配列Array.append()のように追加はできないので現在の値を取り出して結合して上書きする)
               self.tweetListModel.relayItems?.accept(newValue)
           })
           .disposed(by: disposeBag)
   }
}

実装してみて思うこと

View側でUITableViewで利用するデフォルトセル(UITabelViewCell)の登録は行ってもよかったのかなと思います。

正直50:50で考えてはおりますね。
View側にはユーザーにとって見える処理やレイアウトなどを記載するべきなので、Controllerにデフォルトセル(UITabelViewCell)の登録を行うのは正しいことなのかもしれません。

ソースコード

上記git urlのタグ2.0.0をご参考ください。

さいごに

ご指摘や質問などあればコメントまでお願い致します。


この記事が気に入ったらサポートをしてみませんか?