ios漫漫学习路

Swift 踩坑笔记(五)—— UITableView Cell

2018-09-13  本文已影响553人  黑羽肃霜

综述

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

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

遇到的问题


先明确几个概念

重申 Cell 的复用机制和使用

简单的来说,tableview 的复用机制是我们在 cellForRowAtIndexPath 的一系列操作。

很简单的情况是,如果我们不每次滚动的时候去dataSource数组中把对应index的数值取出来,只管的感受就是UI虽然固定,但是数据和图片一直在乱跑

鉴于Swift 无法自定义cell的初始化,那么上下滚动时,怎么重新赋值而不重复绘制就显得格外重要。

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

我们要讨论的是在Cell复用过程中的赋值和 UI 重叠的问题。

典型案例 —— Cell 的 UI 内容根据数据而定

描述

根据上面所说的,CellUI 在被创建后,就会被放进复用池中,等待被重用。但是如果像下面这种情况:

一个TableView 中每个Cell 的内容是根据数据中数组的个数来渲染的,就会出问题:

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

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

这样会造成一个很大的问题:

来看下错误的现象图

局部刷新的效果

局部刷新的效果.gif

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

image.png

正确的代码

为了避免混淆,我这里就不贴原来错误的代码了。

来看下面正确的代码

// 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,先把这几个 subViewremoveFromSuperView 避免干扰,然后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)
        }
    }

下面是讲解:


刷新的问题

先来说说 reloadData的缺点

局部刷新的问题

鉴于上面讲的reloadData,我们很自然的就会想到使用局部刷新来做。

tableview.beginUpdates()
tableview.reloadRows(at: tableview.indexPathsForVisibleRows!, with: .none)
tableview.endUpdates()

实际上和 reload 没有太多的差异,只是注意局部刷新,会创建新的Cell

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


因为之前对重用机制的理解存在误区,所以文章内容更新了。

上一篇下一篇

猜你喜欢

热点阅读