
对于 iOS 开发来说, UITableView 无疑是一把牛刀. 今天我想介绍的是怎么让这把牛刀像小刀一样好使.
我们想实现的目标是, 对于一些简单的场景,比如一个小的列表,可以像如下这么使用:
let dataSource = ListDataSource(dataItems:[item1,item2,...]) tableView.dataSource = dataSource OK, 我们开始吧.
UITableViewDataSource 协议的类.由于 UITableViewDataSource 协议扩展了 NSObjectProtocl, 所以我们的类最好继承自 NSObject.
class ListDataSource: NSObject, UITableViewDataSource{ } UITableViewDataSource 要求的两个方法:class ListDataSource: NSObject, UITableViewDataSource{ public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return 0 } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ return UITableViewCell(style: .default, reuseIdentifier: "cell") } } 这里就遇到问题了,我们的数据是动态的,不能直接返回 0. 我们的 cell 也不是这样直接一个 UITableViewCell 类啊. 问题是,我们不知道数据是什么,怎么构造 Cell?
numberOfRowsInSection 这个方法. 这里我们只用到一个 Section, 我们的数据最常见的是一个数组. 那返回这个数组的大小即可. 但是数组类型我们不知道, 于是我们声明为泛型 . 如下:于是 ListDataSource 变成了这样:
public class ListDataSource<T>: NSObject, UITableViewDataSource{ public private(set) var dataItems: [T] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ return UITableViewCell(style: .default, reuseIdentifier: "cell") } } UITableViewCell 子类.解决了上面问题, 同理我们的 UITableViewCell 是不是也可以声明为泛型呢?
于是变成这样:
public class ListDataSource<T,Cell: UITableViewCell>: NSObject, UITableViewDataSource{ public private(set) var dataItems: [T] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ return Cell(style: .default, reuseIdentifier: "cell") } } 上面我们对于 Cell 泛型也增加了基本的限制,就是 Cell 是继承自 UITableViewCell 的.
但是这里有一个问题就是, 我们的数据跟 Cell 还没有关联起来呢! 也就是说我们需要 Cell 有关联 数据模型 T 的能力. 上面这个需求体现了两点, 一表明某个能力, 即满足某协议约束. 对于泛型来说,可以增加需要实现的协议作为约束.
于是我们要求,Cell 应该实现如下协议, 可以绑定某一个数据.
public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } 因为我们不知道要绑定的数据类型是什么, 所以我们只能先给一个名字. DataItem. 由实现者确定类型是什么. 也相当是一种泛型.
同时这个 DataItem 应该等于之前的数据泛型 T. 这样,我们得到如下的 ListDataSource
public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } public class ListDataSource<T,Cell: UITableViewCell>: NSObject, UITableViewDataSource where Cell: Bindable, Cell.DataItem == T{ public private(set) var dataItems: [T] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let cell = Cell(style: .default, reuseIdentifier: "cell") cell.bind(to: dataItems[indexPath.row]) return cell } } 重点在于 where Cell: Bindable, Cell.DataItem == T 也就是我们上面所说的. 要求 Cell 实现 Bindable 协议.
T 声明.之前由于不确定数据模型的类型,所以我们引入了 T 作为泛型名. 但是后来我们又添加了 Bindable, 其中使用 DataItem 名来标识数据类型. 这里两者相等,看起来重复了. 简化后如下:
public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } public class ListDataSource<Cell: UITableViewCell>: NSObject, UITableViewDataSource where Cell: Bindable{ public private(set) var dataItems: [Cell.DataItem] = [] public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let cell = Cell(style: .default, ruseIdentifier: "cell") cell.bind(to: dataItems[indexPath.row]) return cell } } ReuseableCell 模型.public protocol Bindable{ associatedtype DataItem func bind(to item: DataItem) } extension String{ public static let listCellReuseIdentifier = "listCell" } public class ListDataSource<Cell: UITableViewCell>: NSObject, UITableViewDataSource where Cell: Bindable{ public private(set) var dataItems: [Cell.DataItem] = [] private weak var tableView:UITableView? public init(dataItems: [Cell.DataItem]){ self.dataItems = dataItems } public func bind(to tableView: UITableView){ self.tableView = tableView tableView.dataSource = self tableView.register(Cell.classForCoder(), forCellReuseIdentifier: .listCellReuseIdentifier) } public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return dataItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{ let cell = tableView.dequeueReusableCell(withIdentifier: .listCellReuseIdentifier, for: indexPath) as! Cell cell.bind(to: dataItems[indexPath.row]) return cell } } 有了上面的代码之后, 我们再显示一个 List 就简单得多了. 例如我们显示一个菜单列表.
// 先定义菜单 Cell, 这是数据模型类型是 String class MenuCell: UITableViewCell,Bindable{ typealias DataItem = String func bind(to item: String) { textLabel?.text = item } } // 实例化一个 ListDataSource let dataSource = ListDataSource<MenuCell>(dataItems: [ "排骨面","红椒面", "青椒面" ]) let tableView = UITableView(frame: .zero, style: .plain) // 将 DataSource 与 UITableView 绑定. dataSource.bind(to: tableView) 怎么样? 最后实现的结果 ,跟我们的希望应该是差不多的了. 当然这只是一个开始, 为了适用你项目的使用, 你可以在上面的代码的基础上. 添加修改. Hope to see your advice!
1 HuangLibo 2016-12-28 21:48:09 +08:00 table view 是牛刀, 那 collection view 岂不是核武器了 |
2 FreshOldMan 2016-12-28 21:49:10 +08:00 大哥,每天一篇啊 |