Swift 踩坑笔记 —— UITableView Cell初始化和刷新的问题探讨

栏目: Swift · 发布时间: 6年前

内容简介:讲到闲话少说,最近在写关于

讲到 UITableView ,大家一定都不陌生。有一个相对夸张的说法,叫做学好 UITableView ,你就是一名合格的 iOS 工程师

闲话少说,最近在写 Swift 的过程中碰到了以下几个问题,特别在此记录。

遇到的问题

  • cellForRowAtIndexPath 代理中,对 cell (尤其是自定义 cell ) 的初始化异同
    • OC 的区别 —— 不能使用 OC 的那种判空方式来初始化
    • 初始化不能使用自定义的方法 —— 通过 dequeue 方法得到的 cell 永远都是非空的,换言之,即便你自定义了一个初始化方法,它也不会被执行到。
    • 通过渲染方式(render)来绘制图像,赋值
  • 刷新的问题
    • 使用 reloadData 时候,在 iOS 11 上会产生抖动
    • 慎用局部刷新 reloadRows 的相关方法,会造成 cell 复用混乱
    • insertRowdeleteRowreloadRows 一样都属于局部刷新的范畴,需要用 beginUpdateendUpdate 包起来。这上上一点类似

先明确两个概念

  • 代码中的 setup 表示只会执行一次,而且在 cell 的初始化中表示他的绘图(不带数据)也只会执行一次
  • 代码中的 render 表示渲染,实际上是意味着 setup 已经完成了绘图,我要在每次重用时把数据传进去渲染

初始化问题 cellForRowAtIndexPath

关于 cellForRowAtIndexPath 的初始化问题其实在这篇文章中已经讨论过,这里不作赘述 Swift 踩坑笔记(二)—— 初始化Tableview 及自定义 TableviewCell

我们要讨论的是在 cell 复用过程中的赋值问题。

简单的来说,tableview 的复用机制是我们在 cellForRowAtIndexPath 初始化时绘制好必要的控件及相关约束,但是并不会去赋值。因为每次上下滚动都会重新从复用池中取出 cell,将 DataSource 对应的数据赋值一次 . 鉴于 Swift 无法自定义 cell 的初始化,那么上下滚动时,怎么重新赋值而不重复绘制就显得格外重要。

来看下面的代码

// 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
}
复制代码

再来看下面的 Cell 示意图

Swift 踩坑笔记 —— UITableView Cell初始化和刷新的问题探讨

我们这里的 Cell 分了很多层级,

除了顶部的 Header 区域是固定知道的高度外,下面的 区域 InfoA, InfoB, InfoC ... 等等,都是根据具体的信息去绘制的。 换言之,我不知道每个 Cell 具体要画几个 InfoX

普通的 cell 初始化,只能满足固定的视图,这种动态的,只能用 cell 自定义一个 render 方法来做了。

再来看下面的这段 自定义 Cell 的代码

// 略去类的初始化,这里为了  render 的目的,必须去持有这两块视图
    private var headerBaseInfoView: IGGIDBaseInfoView
    private var infoViews: [infoView] = []

    public func renderCell(info: IGGIDAccountModel) {
        headerBaseInfoView.render(renderInfo: info.baseInfo)
        renderInfoViews(info.infos)
    }
    
    private func renderInfoViews(_ infos: [someInfoModel]?) {
        guard let infos = infos, infos.count > 0 else { return }
        
        // 如果已经创建过了,那么只要赋值就可以了
        if (infoViews.count > 0) {
            for (index, bindInfo) in bindInfos.enumerated() {
                let infoView = infoViews[index]
                infoView.render(info)
            }
        } else {
            setupInfoViews(infos) // 如果没有持有,表示初次渲染,这时候要赋值
        }
    }
    
    private func setupPlatformBindInfoViews(_ infos: Array<Info>) {
        for (index, bindInfo) in infos.enumerated() {
            let infoView = InfoView()
            containerView.addSubview(infoView)
            infoView.snp.makeConstraints { (make) in
                //... 写约束
            }
            infoViews.append(infoView)
            infoView.render(info) // 记得再去调用一次
        }
    }
复制代码

下面是讲解:

  • 类中要去持有那些会被渲染的视图,作为属性内容。
  • headerBaseInfoView 是固定的内容,所以实际上我们在重写他的初始化方法的时候,直接就把 setupUI() (只会执行一次)这个绘图的工作做掉了
  • infoViews 属于我一开始没办法知道你有几个,所以我无法初始化。因此我只能在 render 的时候做下面两个操作:
    • 先对 infoViews 判空,不存在,那么就做第一次的 setupUI 操作,这时候根据 model 也已经知道数量了,先把绘图工作做了。
      • 绘图完毕之后,也要做一次正规的 render 操作,不然会没数据,变成空白
    • 完成了 setupUI 之后,刷新数据

综上所述,就是和概念中说的一样:

cell 的操作都是先通过 setupUI 绘制视图,然后去 render 把数值赋过去。只是一些条件下,我们不知道要画几个,那么只能在 render 的时候根据当前数据,补上 setupUI 之后,再去做真正的 render

刷新的问题

先来说说 reloadData 的缺点

  • 性能问题 我们都知道, UITableviewreloadData 是需要慎用的。因为他会将整个 tableview 都刷新一遍。这意味着也许我只需要刷新2个 cell ,你却让所有的 cell 都重渲染了一遍。从性能而言这显然是不可取的。 所以我们才会想到去用局部刷新。

  • reloadData 无法像系统提供的其他刷新方法一样,带有 animate 参数,这让刷新时,整个页面看起来非常突兀。如果你不自己加动画,那么体验真的不太好

  • iOS 11 上会有一个问题,就是重载之后页面会乱跑:

    Swift 踩坑笔记 —— UITableView Cell初始化和刷新的问题探讨
    • 解决办法: 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()
复制代码

然而,事实比我们想象的要残酷:

局部刷新的效果

Swift 踩坑笔记 —— UITableView Cell初始化和刷新的问题探讨

使用 reveal 查看,发现多了一个层级,盖在应该有的位置

Swift 踩坑笔记 —— UITableView Cell初始化和刷新的问题探讨

我查看了官方的文档,也自己做了断点调试,发现:

不会创建新的cell
cell

下面两篇文章也提到了类似的问题。参考文章一 慎用局部刷新

目前来看,似乎是 OC 下不会出问题, swift 3 (我用的是4.0的版本)下会有这个 bug。 目前还是先使用 reloadData 的全局刷新替换局部刷新,希望后续会有更好的办法

如果有更好的办法解决,欢迎告知。


以上所述就是小编给大家介绍的《Swift 踩坑笔记 —— UITableView Cell初始化和刷新的问题探讨》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Designing Data-Intensive Applications

Designing Data-Intensive Applications

Martin Kleppmann / O'Reilly Media / 2017-4-2 / USD 44.99

Data is at the center of many challenges in system design today. Difficult issues need to be figured out, such as scalability, consistency, reliability, efficiency, and maintainability. In addition, w......一起来看看 《Designing Data-Intensive Applications》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换