はじめに
いまプロトコル指向で実装するのが流行りということでどういうのか調べていましたが、WWDCの動画だと自分には難しかったので、ちょうどわかりやすい例がはてブにでていたので訳してみました
以下翻訳
Swiftのプロトコル拡張とジェネリクスを使ってiOSのセルの登録と再利用する
A common task when developing iOS apps is to register custom cell subclasses for both UITableView
and UICollectionView
. Well, that is if you don’t use Storyboards, of course.
iOSアプリを開発している時によくあるタスクとして、Storyboardを使わないのであれば、'UITableView'や
'UICollectionView'のためにカスタムセルサブクラスを実装する
Both UITableView
and UICollectionView
offer a similar API to register custom cell classes:
UITableView
やUICollectionView
は似たようなカスタムセルクラスの登録APIを提供している
public func registerClass(cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String)
public func registerNib(nib: UINib?, forCellWithReuseIdentifier identifier: String)
A widely accepted solution to handle cell registration and dequeuing is to declare a constant for the reuse identifier:
広く受け入れられた解決方法はreuseIdentifierを宣言してセルの登録処理とdequeuingを実装する
private let reuseIdentifier = "BookCell"
class BookListViewController: UIViewController, UICollectionViewDataSource {
@IBOutlet private weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "BookCell", bundle: nil)
self.collectionView.registerNib(nib, forCellWithReuseIdentifier: reuseIdentifier)
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
if let bookCell = cell as? BookCell {
// TODO: configure cell
}
return cell
}
}
Let’s try to generalize this code and make it simpler and safe.
じゃあこのコードをより単純に安全にしてみよう!
First of all, it would be nice to get away with declaring a constant for every reuse identifier in our app. We can just use the name of the custom cell class as a default reuse identifier.
We can create a protocol for Reusable Views and provide a default implementation constrained to UIView
subclasses.
まず、アプリ内でいちいちreuseIdentifierを宣言するのをやめるのがよさそうだ。
私たちはデフォルトのreuseIdentifierとしてカスタムセルのクラス名を使うことにする。
なので再利用できるViewのためのプロトコルを作って、UIViewのサブクラスに強制的にその機能を提供することができる
protocol ReusableView: class {
static var defaultReuseIdentifier: String { get }
}
extension ReusableView where Self: UIView {
static var defaultReuseIdentifier: String {
return NSStringFromClass(self)
}
}
extension UICollectionViewCell: ReusableView {
}
By making UICollectionViewCell
conform to the ReusableView
protocol, we get a unique reuse identifier per cell subclass.
UICollectionViewCellはReuseableView
プロトコルに従うことによって、それぞれのセルのサブクラスから固有のreuseIdentifierを取得することができる
let identifier = BookCell.defaultReuseIdentifier
// identifier = "MyModule.BookCell"
Next, we can get rid of the hard-coded string we are using to load the Nib.
次に、Nibを読み込むためにハードコードしてる文字列を取り除くことができる
Let’s create a protocol for Nib Loadable Views and provide a default implementation using protocol extensions.
じゃあNibを読み込むためのViewsためのプロコトルを作って、プロトコル拡張を使ってその機能を提供できるようにしよう!
protocol NibLoadableView: class {
static var nibName: String { get }
}
extension NibLoadableView where Self: UIView {
static var nibName: String {
return NSStringFromClass(self).componentsSeparatedByString(".").last!
}
}
extension BookCell: NibLoadableView {
}
By making our BookCell
class conform to the NibLoadableView
protocol we now have a safer way to get the Nib name.
このBookCell
クラスはNibLoadeableView
に従うことで、より安全にNibの名前を手に入れることができる
let nibName = BookCell.nibName
// nibName = "BookCell"
If you use a different name for the XIB file than the one provided by Xcode, you can always override the default implementation of the nibName property.
Xcodeから提供されたXIBファイルに違う名前を使いたいなら、いつでもnibName
プロパティの機能をオーバーライドできる
With these two protocols in place, we can use Swift Generics and extend UICollectionView
to simplify cell registration and dequeuing.
この2つのプロトコルを使って、セルの登録処理とdequeuingをUICollectionView
を簡単に拡張し、Swiftジェネリクスを使える
extension UICollectionView {
func register<T: UICollectionViewCell where T: ReusableView>(_: T.Type) {
registerClass(T.self, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UICollectionViewCell where T: ReusableView, T: NibLoadableView>(_: T.Type) {
let bundle = NSBundle(forClass: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
registerNib(nib, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
}
func dequeueReusableCell<T: UICollectionViewCell where T: ReusableView>(forIndexPath indexPath: NSIndexPath) -> T {
guard let cell = dequeueReusableCellWithReuseIdentifier(T.defaultReuseIdentifier, forIndexPath: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
}
return cell
}
}
Notice that we created two versions of the register method, one for cell subclasses implementing just ReusableView
and another one for cell subclasses implementing both ReusableView
and NibLoadableView
. This nicely decouples the view controller from the specific cell registration method.
登録メソッドの2つのバージョンを作ってみて、セルのサブクラスの実装の1つは単にReusableView
を実装しているもの、もう片方がReusableView
とNibLoadableView
の2つを実装してるものがあることに気づく。
これは特定のセルの登録メソッドからViewControllerをうまく分離している
Another nice detail is that the dequeueReusableCell
method doesn’t need to take any reuse identifier and uses the cell subclass type for the return value.
ほかにもdequeueReusableCell
メソッドはreuseIdentifierをとる必要がなく、セルのサブクラスの返り値を使っているのはいいdetailだ
Now the cell registration and dequeuing code looks much better :).
セルの登録処理とdequeuingはいまや見違えるようだ( ・`ω・´)
class BookListViewController: UIViewController, UICollectionViewDataSource {
@IBOutlet private weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.register(BookCell.self)
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: BookCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
// TODO: configure cell
return cell
}
...
}
Conclusion
If you are coming from Objective-C it is worth to investigate powerful Swift features like Protocol Extensions and Generics to find alternate and more elegant ways to deal with Cocoa classes.
##最後に
もし君がObjective-Cから来たのであれば、SwiftのすばらしくパワフルなProtocolやGenericsといった機能を調べたり、違いを見つけたり、Cocoaクラスによりエレガントな方法を適用するのは有意義だろう
以上翻訳