Swift 踩坑筆記(五)——?UITableView Cell初始化和刷新的問題探討

綜述

講到 UITableView,大家一定都不陌生算墨。有一個相對夸張的說法净嘀,叫做學(xué)好 UITableView侠讯,你就是一名合格的iOS 工程師

閑話少說厢漩,最近在寫 Swift 的過程中碰到了以下幾個問題,特別在此記錄柴底。

遇到的問題

  • cellForRowAtIndexPath 代理中柄驻,對 cell(尤其是自定義cell) 的初始化異同
    • OC的區(qū)別 —— 不能使用OC的那種判空方式來初始化
    • 初始化不能使用自定義的方法 —— 通過dequeue方法得到的cell 永遠都是非空的焙压,換言之,即便你自定義了一個初始化方法野哭,它也不會被執(zhí)行到拨黔。
    • 通過渲染方式(render)來繪制圖像绰沥,賦值
    • 理解cell的復(fù)用機制
  • 刷新的問題
    • 使用 reloadData時候徽曲,在iOS 11 上會產(chǎn)生抖動
    • insertRowdeleteRowreloadRows 一樣都屬于局部刷新的范疇,局部刷新時涧衙,系統(tǒng)會創(chuàng)建一個新的cell來,并和舊的cell在刷新時來回切換雁比。

先明確幾個概念

  • 代碼中的 setup 表示只會執(zhí)行一次章贞,而且在 cell 的初始化中表示他的繪圖(不帶數(shù)據(jù))也只會執(zhí)行一次
  • 代碼中的render 表示渲染非洲,實際上是意味著setup已經(jīng)完成了繪圖两踏,我要在每次重用時把數(shù)據(jù)傳進去渲染

重申 Cell 的復(fù)用機制和使用

簡單的來說,tableview 的復(fù)用機制是我們在 cellForRowAtIndexPath 的一系列操作赡麦。

  • CellUI 一旦被創(chuàng)建泛粹,系統(tǒng)就會存放在復(fù)用池中等待復(fù)用肮疗。
  • Cell 的可變內(nèi)容(通常是labeltextimage的內(nèi)容们衙,選中的背景色等)蒙挑,是不會記錄的愚臀。
  • 刪除某個 Cell 后再創(chuàng)建一個新的 Cell, 實際上你會發(fā)現(xiàn)新的 Cell 中有部分 UI 時舊 Cell中的
  • reloadRows 局部刷新時會創(chuàng)建新的 Cell,再刷新時會和舊的Cell來回切換

很簡單的情況是馋袜,如果我們不每次滾動的時候去dataSource數(shù)組中把對應(yīng)index的數(shù)值取出來桃焕,只管的感受就是UI雖然固定捧毛,但是數(shù)據(jù)和圖片一直在亂跑

鑒于Swift 無法自定義cell的初始化呀忧,那么上下滾動時,怎么重新賦值而不重復(fù)繪制就顯得格外重要胰坟。

關(guān)于 cellForRowAtIndexPath 的初始化問題其實在這篇文章中已經(jīng)討論過泞辐,這里不作贅述
Swift 踩坑筆記(二)—— 初始化Tableview 及自定義 TableviewCell

我們要討論的是在Cell復(fù)用過程中的賦值和 UI 重疊的問題。

典型案例 —— Cell 的 UI 內(nèi)容根據(jù)數(shù)據(jù)而定

描述

根據(jù)上面所說的吹缔,CellUI 在被創(chuàng)建后厢塘,就會被放進復(fù)用池中肌幽,等待被重用。但是如果像下面這種情況:

一個TableView 中每個Cell 的內(nèi)容是根據(jù)數(shù)據(jù)中數(shù)組的個數(shù)來渲染的格嘁,就會出問題:

image.png

我們這里的 Cell 分了很多層級讥蔽,

除了頂部的 Header區(qū)域是固定知道的高度外冶伞,下面的 區(qū)域 InfoA, InfoB, InfoC ...等等步氏,都是根據(jù)具體的信息去繪制的。
換言之芋类,我不知道每個 Cell 具體要畫幾個 InfoX

這樣會造成一個很大的問題:

  • 因為根據(jù)復(fù)用機制侯繁,數(shù)據(jù)是每次都有可能不同的泡躯,而根據(jù)數(shù)據(jù)創(chuàng)建的 UI 一旦被創(chuàng)建丽焊,就會一直存在于復(fù)用池中技健。
  • 如果 Cell 發(fā)生了刪除雌贱,再添加偿短,就有可能將那些不用的Cell UI 復(fù)用進來。
  • 局部刷新時會創(chuàng)建新的 Cell导街,這時候疊加在舊的UI上切換時纤子,就會造成視圖的重疊

來看下錯誤的現(xiàn)象圖

局部刷新的效果

局部刷新的效果.gif

使用 reveal 查看控硼,發(fā)現(xiàn)多了一個層級UI,蓋在應(yīng)該有的位置()

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的部分是固定的锁施,也就是不是動態(tài)變化的 UI悉抵,因此每次render的時候只要重新賦值即可
  • 而下面的infoA, infoB, infoC...是根據(jù)數(shù)值來變化的。我們現(xiàn)在能做的就是對于動態(tài)的 Cell UI傻谁,先把這幾個 subViewremoveFromSuperView 避免干擾列粪,然后setUp重繪一次,再render進賦值力图。

再來看下面的這段 自定義 Cell 的代碼

  // 略去類的初始化吃媒,這里為了  render ,去持有靜態(tài)的 UI
    private var headerBaseInfoView: BaseInfoView = BaseInfoView()

    public func renderCell(info: accountModel) {
    // 除了靜態(tài)的 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() {
            // 創(chuàng)建后渲染數(shù)據(jù)
            let bindInfoView = InfoView()
            bindInfoView.render(bindInfo: bindInfo)
            
            // 布局 (也可以先布局再渲染數(shù)據(jù)氯质,這無所謂)
            contentView.addSubview(bindInfoView)
            bindInfoView.snp.makeConstraints { (make) in
                //這里略去約束的部分
            }
            infoViews.append(bindInfoView)
        }
    }

下面是講解:

  • 類中要去持有靜態(tài)的視圖闻察,作為屬性內(nèi)容。
  • headerBaseInfoView 是固定的內(nèi)容呢灶,所以實際上我們在重寫他的初始化方法的時候钉嘹,直接就把 setupUI()(只會執(zhí)行一次)這個繪圖的工作做掉了
  • infoViews 屬于我一開始沒辦法知道你有幾個,所以我無法初始化缨睡。只在每次渲染數(shù)據(jù)的時候:
    • 先將所有動態(tài)視圖remove
    • 根據(jù)數(shù)據(jù)內(nèi)容重新渲染視圖并賦值(也可以先賦值再渲染數(shù)據(jù)奖年,不影響)

刷新的問題

先來說說 reloadData的缺點

  • 性能問題
    我們都知道沛贪,UITableviewreloadData 是需要慎用的。因為他會將整個tableview 都刷新一遍嗅义。這意味著也許我只需要刷新2個cell隐砸,你卻讓所有的cell都重渲染了一遍季希。從性能而言這顯然是不可取的幽纷。
    所以我們才會想到去用局部刷新友浸。

  • reloadData 無法像系統(tǒng)提供的其他刷新方法一樣偏窝,帶有animate參數(shù),這讓刷新時伦意,整個頁面看起來非常突兀驮肉。如果你不自己加動畫已骇,那么體驗真的不太好

  • iOS 11 上會有一個問題,就是重載之后頁面會亂跑:

    頁面亂跑.gif

    • 解決辦法: google后奈辰,得到的內(nèi)容是說
      Self-Sizing在iOS11下是默認開啟的奖恰,Headers, footers, and cells都默認開啟Self-Sizing宛裕,所有estimated 高度默認值從iOS11之前的 0 改變?yōu)?code>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 沒有太多的差異,只是注意局部刷新粒褒,會創(chuàng)建新的Cell祥款。

下面兩篇文章也提到了類似的問題抠艾。
參考文章一
慎用局部刷新


因為之前對重用機制的理解存在誤區(qū),所以文章內(nèi)容更新了究履。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市欲芹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剑逃,老刑警劉巖蛹磺,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡焰轻,警方通過查閱死者的電腦和手機蝠筑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忆某,你說我怎么就攤上這事×兀” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長磨澡。 經(jīng)常有香客問我,道長厦酬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮醋闭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘囚企。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布厨姚。 她就那樣靜靜地躺著谬墙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪造虎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天浸卦,我揣著相機與錄音,去河邊找鬼时捌。 笑死匣椰,一個胖子當著我的面吹牛蛤奥,可吹牛的內(nèi)容都是我干的凡桥。 我是一名探鬼主播啊掏,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼啡省,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了结序?” 一聲冷哼從身側(cè)響起垃环,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤救赐,失蹤者是張志新(化名)和其女友劉穎只磷,沒想到半個月后钮追,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阿迈,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡刊棕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年甥角,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗤无。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片割疾。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出首有,到底是詐尸還是另有隱情卜壕,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站尺碰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亲桥。R本人自食惡果不足惜固耘,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一悼凑、第九天 我趴在偏房一處隱蔽的房頂上張望璧瞬。 院中可真熱鬧,春花似錦嗤锉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽触菜。三九已至,卻和暖如春哲泊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背切威。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工先朦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锋谐。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓涮拗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親三热。 傳聞我的和親對象是個殘疾皇子就漾,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容