对于 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, reuseIdentifier: "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!
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.