讲到 UITableView
,你们必定都不陌生。有一个相对夸张的说法,叫作学好 UITableView
,你就是一名合格的iOS
工程师swift
闲话少说,最近在写 Swift
的过程当中碰到了如下几个问题,特别在此记录。数组
cellForRowAtIndexPath
代理中,对 cell
(尤为是自定义cell
) 的初始化异同
OC
的区别 —— 不能使用OC
的那种判空方式来初始化dequeue
方法获得的cell
永远都是非空的,换言之,即使你自定义了一个初始化方法,它也不会被执行到。cell
的复用机制reloadData
时候,在iOS 11
上会产生抖动insertRow
和 deleteRow
和 reloadRows
同样都属于局部刷新的范畴,局部刷新时,系统会建立一个新的cell
来,并和旧的cell
在刷新时来回切换。setup
表示只会执行一次,并且在 cell 的初始化中表示他的绘图(不带数据)也只会执行一次render
表示渲染,其实是意味着setup
已经完成了绘图,我要在每次重用时把数据传进去渲染简单的来讲,tableview 的复用机制是咱们在 cellForRowAtIndexPath
的一系列操做。app
Cell
的 UI
一旦被建立,系统就会存放在复用池中等待复用。Cell
的可变内容(一般是label
的text
,image
的内容,选中的背景色等),是不会记录的。Cell
后再建立一个新的 Cell
, 实际上你会发现新的 Cell
中有部分 UI
时旧 Cell
中的reloadRows
局部刷新时会建立新的 Cell
,再刷新时会和旧的Cell
来回切换很简单的状况是,若是咱们不每次滚动的时候去dataSource
数组中把对应index
的数值取出来,只管的感觉就是UI
虽然固定,可是数据和图片一直在乱跑布局
鉴于Swift
没法自定义cell
的初始化,那么上下滚动时,怎么从新赋值而不重复绘制就显得格外重要。性能
关于 cellForRowAtIndexPath
的初始化问题其实在这篇文章中已经讨论过,这里不做赘述 Swift 踩坑笔记(二)—— 初始化Tableview 及自定义 TableviewCell动画
咱们要讨论的是在Cell
复用过程当中的赋值和 UI 重叠的问题。google
根据上面所说的,Cell
的UI
在被建立后,就会被放进复用池中,等待被重用。可是若是像下面这种状况:spa
一个TableView
中每一个Cell
的内容是根据数据中数组的个数来渲染的,就会出问题: 3d
Cell
分了不少层级,
除了顶部的 Header
区域是固定知道的高度外,下面的 区域 InfoA, InfoB, InfoC ...
等等,都是根据具体的信息去绘制的。 换言之,我不知道每一个 Cell 具体要画几个 InfoX
代理
这样会形成一个很大的问题:
Cell
发生了删除,再添加,就有可能将那些不用的Cell UI
复用进来。Cell
,这时候叠加在旧的UI
上切换时,就会形成视图的重叠局部刷新的效果
使用 reveal 查看,发现多了一个层级UI,盖在应该有的位置()
为了不混淆,我这里就不贴原来错误的代码了。
来看下面正确的代码
// tableview 代理
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: someCellID, for: indexPath) as! MyCell
cell.renderCell(info: dataSource[indexPath.row])
return cell
}
复制代码
思路:
- 上面的图中,
Header
的部分是固定的,也就是否是动态变化的 UI,所以每次render
的时候只要从新赋值便可- 而下面的
infoA, infoB, infoC...
是根据数值来变化的。咱们如今能作的就是对于动态的Cell UI
,先把这几个subView
都removeFromSuperView
避免干扰,而后setUp
重绘一次,再render
进赋值。
再来看下面的这段 自定义 Cell
的代码
// 略去类的初始化,这里为了 render ,去持有静态的 UI
private var headerBaseInfoView: BaseInfoView = BaseInfoView()
public func renderCell(info: accountModel) {
// 除了静态的 UI,剩下的都remove 掉,避免重用时的干扰
for view in contentView.subviews {
guard view != headerBaseInfoView else {
continue
}
view.removeFromSuperview()
}
headerBaseInfoView.render(renderInfo: info.baseInfo!)
setupAndRenderInfoViews(bindInfos)
}
private func setupAndRenderInfoViews(_ bindInfos: [infoModel]) {
var infoViews: [infoView] = []
for (index, bindInfo) in bindInfos.enumerated() {
// 建立后渲染数据
let bindInfoView = InfoView()
bindInfoView.render(bindInfo: bindInfo)
// 布局 (也能够先布局再渲染数据,这无所谓)
contentView.addSubview(bindInfoView)
bindInfoView.snp.makeConstraints { (make) in
//这里略去约束的部分
}
infoViews.append(bindInfoView)
}
}
复制代码
下面是讲解:
setupUI()
(只会执行一次)这个绘图的工做作掉了remove
掉reloadData
的缺点性能问题 咱们都知道,UITableview
中 reloadData
是须要慎用的。由于他会将整个tableview
都刷新一遍。这意味着也许我只须要刷新2个cell
,你却让全部的cell
都重渲染了一遍。从性能而言这显然是不可取的。 因此咱们才会想到去用局部刷新。
reloadData
没法像系统提供的其余刷新方法同样,带有animate
参数,这让刷新时,整个页面看起来很是突兀。若是你不本身加动画,那么体验真的不太好
在 iOS 11
上会有一个问题,就是重载以后页面会乱跑:
解决办法: google
后,获得的内容是说 Self-Sizing在iOS11下是默认开启的,Headers, footers, and cells都默认开启Self-Sizing,全部estimated 高度默认值从iOS11以前的 0 改变为UITableViewAutomaticDimension
if #available(iOS 11.0, *) {
taleview.estimatedRowHeight = 0
taleview.estimatedSectionHeaderHeight = 0
taleview.estimatedSectionFooterHeight = 0
}
复制代码
鉴于上面讲的reloadData
,咱们很天然的就会想到使用局部刷新来作。
tableview.beginUpdates()
tableview.reloadRows(at: tableview.indexPathsForVisibleRows!, with: .none)
tableview.endUpdates()
复制代码
实际上和 reload 没有太多的差别,只是注意局部刷新,会建立新的Cell
。
由于以前对重用机制的理解存在误区,因此文章内容更新了。