UITableView
Swift
RxSwift

SwiftでGenericsを使ったTableViewの実装


SwiftでGenericsを使ったTableViewの実装

モバイルアプリ開発エキスパート養成読本:書籍案内|技術評論社 中で紹介されている、「タイプセーフでモダンなiOSアプリの設計|UITableViewとUITableViewCell周りの処理」の内容を自分のアプリに適用してみたのでその内容を書き留めておく。

xibファイルを使ったカスタムcellをキャストせずに使えるよう実装。


RxSwiftからの利用を試す。


Nibableプロトコルを採用したセルの作成

カスタムCellを利用する場合、UITableViewへの登録を、UINib、reuseIdentifierを使って行うが、これらを指定する際には文字列を渡す必要がある。これはコンパイラが発見できないバグを埋め込む可能性がある。この問題を防ぐため、nib名をセル自身にReuseIdentifier、nib名を返却させるような実装をProtocol、Extentionを使って再利用可能な形で実装する。

まずはnibName, nib を返却するProtocol。クラス名を返す実装はExtension内に定義することで、このProtocolを採用したクラスで必要に応じてクラス名以外を返すよう変更可能。こうすることで再利用性を高めている。

import Foundation

import UIKit

protocol Nibable: NSObjectProtocol {
static var nibName: String { get }
static var nib: UINib { get }
}

extension Nibable {
static var nibName: String {
return String(describing: self)
}
static var nib: UINib {
return UINib(nibName: nibName, bundle: Bundle(for: self))
}
}


UITableViewとUITableViewCellの拡張

UITableViewへの登録、UITableViewからのキャスト無しでのCellの取得両方を、カスタムCellのTypeを渡すだけで可能になるようにUITableViewを拡張する。

import Foundation

import UIKit

extension UITableView {
func register<T: UITableViewCell>(_ cellType: T.Type) where T: Nibable {
register(T.nib, forCellReuseIdentifier: T.identifier)
}

func register<T: UITableViewCell>(_ cellType: T.Type) {
register(T.self, forCellReuseIdentifier: T.identifier)
}

func dequeueReusableCell<T: UITableViewCell>(with cellType: T.Type,
for indexPath: IndexPath) -> T {
// swiftlint:disable force_cast
return dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as! T
// swiftlint:enable force_cast
}
}

ReuseIdentifierはStringを渡す必要がありこれもコンパイラが発見できないバグを埋め込む可能性がある。この問題を解決するために、UITableViewCellからクラス名を返すよう拡張する。

import Foundation

import UIKit

extension UITableViewCell {
static var identifier: String {
return String(describing: self)
}
}


カスタムCellの実装

作成したNibableプロトコルを採用したカスタムCellを実装する。

import UIKit

class MicropostTableViewCell: UITableViewCell, Nibable {
func configure(micropost: Micropost) {
textLabel?.text? = micropost.content
detailTextLabel?.text? = String(micropost.userId)
}

}

このように作成したメソッドを、外部からはキャストすることなく呼び出せる。RxSwiftから利用した例を以下に。


RxSwiftからの利用

ViewModelが持つVariableをDriverに変換してfeedTableView.rx.itemsにバインドしている。(feedTableViewは、フィード表示用にViewControllerで持っているTableView。)

viewModel.feeds.asDriver().drive(

feedTableView.rx.items(cellIdentifier: MicropostTableViewCell.identifier,
cellType: MicropostTableViewCell.self),
curriedArgument: { row, micropost, cell in
cell.configure(micropost: micropost)
}).disposed(by: disposeBag)

取り出したcellに対してはキャストすることなく独自実装したconfigureメソッドが呼び出せている。


その他

今回作ったものはこちら。

RxSwiftRailsTutorial_RxSwiftRailsTutorial at table-view · kfurue_RxSwiftRailsTutorial