UITableView 的两种复用 cell 方法的区别
做过 iOS 开发的人都知道, iOS 的 UITableView 的 Cell 需要复用, 复用的时候有两种方法可以调用
那么他们到底有什么区别?
之前没有深究过这个问题, 每次用的时候只要使用了 register(_:forCellReuseIdentifier:)
就能保证 App 的效果绝对不会有问题. 前两天心血来潮, 总觉得他们一定有什么不同, 尽管他们只差了一个参数而已
从 Apple 的 Documentation 中是很难看出区别的, 直到我看了 stackoverflow 上的答案. 区别有两点:
- 如果没有使用
register(_:forCellReuseIdentifier:)
dequeueReusableCell(withIdentifier:)
: 会返回nil
dequeueReusableCell(withIdentifier:for:)
: 会直接 crash!
dequeueReusableCell(withIdentifier:for:)
会在初始化时寻找代理方法tableView(_:heightForRowAt:)
返回的高度, 从而使我们的cell 在 cell 初始化时就可以知道其本身的高度(有时这会非常有用).
得到答案之后, 我们再回头看 Apple 的文档
dequeueReusableCell(withIdentifier:)
… This method dequeues an existing cell if one is available or creates a new one using the class or nib file you previously registered. If no cell is available for reuse and you did not register a class or nib file, this method returns nil.
If you registered a class for the specified identifier and a new cell must be created, this method initializes the cell by calling its
init(style:reuseIdentifier:)
method. For nib-based cells, this method loads the cell object from the provided nib file. If an existing cell was available for reuse, this method calls the cell’sprepareForReuse()
method instead.dequeueReusableCell(withIdentifier:for:)
Important: You must specify a cell with a matching identifier in your storyboard file. You may also register a class or nib file using the
register(_:forCellReuseIdentifier:)
method, but must do so before calling this method.
很明显, dequeueReusableCell(withIdentifier:for:)
的文档着重强调我们一定要在使用该方法前调用 register(_:forCellReuseIdentifier:)
, 为的就是防止 crash!
示例
我们可以像下面这样使用 dequeueReusableCell(withIdentifier:)
, 在没有 register 的情况下手动调用指定 UITableViewCell
的初始化方法:
class TestTableViewVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
// MARK: - TableView Delegate & Data Source
extension TestTableViewVC {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as? TestTableViewCell ?? .init(style: .default, reuseIdentifier: "TableViewCell")
cell.setContent(with: indexPath.row.description)
return cell
}
}
但是如果使用 dequeueReusableCell(withIdentifier:for:)
的话, 必须与 register(_:forCellReuseIdentifier:)
配合使用
class TestTableViewVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TestTableViewCell.self, forCellReuseIdentifier: "TableViewCell")
}
}
// MARK: - TableView Delegate & Data Source
extension TestTableViewVC {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TestTableViewCell
cell.setContent(with: indexPath.row.description)
return cell
}
}
总结
啰嗦了那么多, 其实总结下来很简单:
- 从初始化角度来看
- 如果在调用两者之前已经使用了
register(_:forCellReuseIdentifier:)
, 两者没区别; - 如果在调用两者之前没有使用
register(_:forCellReuseIdentifier:)
, 那么dequeueReusableCell(withIdentifier:)
还可以给你一次自我拯救的机会, 但是dequeueReusableCell(withIdentifier:for:)
则会直接 crash, 不会给你任何机会
- 如果在调用两者之前已经使用了
- 从约束布局角度来看
dequeueReusableCell(withIdentifier:)
进行初始化时直接使用默认行高 44dequeueReusableCell(withIdentifier:for:)
会在初始化时寻找代理方法tableView(_:heightForRowAt:)
(如果有的话) 返回的高度
坑
在看到这个 stackoverflow 时, 一个回答说:
dequeueReusableCell(withIdentifier:)
: 在tableView(_:cellForRowAt:)
方法返回 cell 后添加到 superview 中dequeueReusableCell(withIdentifier:for:)
: 在tableView(_:cellForRowAt:)
方法返回 cell 前添加到 superview 中
经过我的验证, 这是完全错误的, 两种方法都是在 tableView(_:cellForRowAt:)
返回 cell 后才会被添加到 superview 上, 可能在之前的某个系统版本上出现过这样的 bug, 但是现在这个 bug 已经被解决了
Ref
本博客文章采用 CC 4.0 协议,转载需注明出处和作者。