UITableView

UITableView 原理

  • UITableView是UIScrollView的子類,所以它能夠自動響應滾動事件(通常爲上下滾動)。
  • 它內部包含0到多個UITableViewCell對象,每一個table cell展現各自的內容。當新cell須要被顯示時,就會調用tableView:cellForRowAtIndexPath:方法來獲取或建立一個cell;而不可視時,它又會被釋放。因而可知,同一時間其實只須要存在一屏幕的cell對象便可,不須要爲每一行建立一個cell。
  • 此外,UITableView還能夠分爲多個sections,每一個區段均可以有本身的head、foot和cells。而在定位一個cell時,就須要2個字段了:在哪一個section,以及在這個section的第幾行。這在iOS SDK中是用NSIndexPath來表述的,UIKit爲其添加了indexPathForRow:inSection:這個建立方法。

UITableView 的簡單的優化

  1. 使用不透明視圖
    不透明的視圖能夠極大地提升渲染的速度。所以如非必要,能夠將table cell及其子視圖的opaque屬性設爲YES(默認值)。
    其中的特例包括背景色,它的alpha值應該爲1(例如不要使用clearColor);圖像的alpha值也應該爲1,或者在畫圖時設爲不透明。
  2. 不要重複建立沒必要要的table cell
    前面說了,UITableView只須要一屏幕的UITableViewCell對象便可。所以在cell不可見時,能夠將其緩存起來,而在須要時繼續使用它便可。
    而UITableView也提供了這種機制,只須要簡單地設置一個identifier便可:git

    1
    2
    3
    4
    5
    static NSString *CellIdentifier = @"xxx";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
  3. 不要作多餘的繪製工做。
    在實現drawRect:的時候,它的rect參數就是須要繪製的區域,這個區域以外的不須要進行繪製。
    例如上例中,就能夠用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判斷是否須要繪製image和text,而後再調用繪製方法。github

    優化UITableViewCell高度計算的那些事

    下面是轉載於 BLOG: SUNNYXX – 優化UITableViewCell高度計算的那些事
    他們也在維護一個開源的擴展,UITableView+FDTemplateLayoutCell,讓高度計算這個事情變的史無前例的簡單,也受到了不少星星的支持,github連接請戳我緩存

UITableViewCell高度計算

rowHeightide

UITableView是咱們再熟悉不過的視圖了,它的 delegate 和 data source 回調不知寫了多少次,也難免遇到 UITableViewCell 高度計算的事。UITableView 詢問 cell 高度有兩種方式。
一種是針對全部 Cell 具備固定高度的狀況,經過:工具

1
self.tableView.rowHeight = 88;

 

上面的代碼指定了一個全部 cell 都是 88 高度的 UITableView,對於定高需求的表格,強烈建議使用這種(而非下面的)方式保證沒必要要的高度計算和調用。rowHeight屬性的默認值是 44,因此一個空的 UITableView 顯示成那個樣子。優化

另外一種方式就是實現 UITableViewDelegate 中的:ui

1
2
3
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 88;
}

須要注意的是,實現了這個方法後,rowHeight 的設置將無效。因此,這個方法適用於具備多種 cell 高度的 UITableView。spa

estimatedRowHeight
這個屬性 iOS7 就出現了, 文檔是這麼描述它的做用的:設計

If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.code

恩,聽上去蠻靠譜的。咱們知道,UITableView 是個 UIScrollView,就像平時使用 UIScrollView 同樣,加載時指定 contentSize 後它才能根據本身的 bounds、contentInset、contentOffset 等屬性共同決定是否能夠滑動以及滾動條的長度。而 UITableView 在一開始並不知道本身會被填充多少內容,因而詢問 data source 個數和建立 cell,同時詢問 delegate 這些 cell 應該顯示的高度,這就形成它在加載的時候浪費了多餘的計算在屏幕外邊的 cell 上。和上面的 rowHeight 很相似,設置這個估算高度有兩種方法:

1
2
3
4
5
self.tableView.estimatedRowHeight = 88;
// or
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// return xxx
}

 

有所不一樣的是,即便面對種類不一樣的 cell,咱們依然可使用簡單的 estimatedRowHeight 屬性賦值,只要總體估算值接近就能夠,好比大概有一半 cell 高度是 44, 一半 cell 高度是 88, 那就能夠估算一個 66,基本符合預期。

  1. 設置估算高度後,contentSize.height 根據「cell估算值 x cell個數」計算,這就致使滾動條的大小處於不穩定的狀態,contentSize 會隨着滾動從估算高度慢慢替換成真實高度,肉眼可見滾動條忽然變化甚至「跳躍」。
  2. 如果有設計很差的下拉刷新或上拉加載控件,或是 KVO 了 contentSize 或 contentOffset 屬性,有可能使表格滑動時跳動。
  3. 估算高度設計初衷是好的,讓加載速度更快,那憑啥要去侵害滑動的流暢性呢,用戶可能對進入頁面時多零點幾秒加載時間感受不大,可是滑動時實時計算高度帶來的卡頓是明顯能體驗到的,我的以爲還不如一開始都算好了呢(iOS8更過度,即便都算好了也會邊劃邊計算)

UITableView+FDTemplateLayoutCell

使用UITableView+FDTemplateLayoutCell 無疑是解決算高問題的最佳實踐之一,既有 iOS8 self-sizing 功能簡單的 API,又能夠達到 iOS7 流暢的滑動效果,還保持了最低支持 iOS6。
使用起來大概是這樣:

<uitableview+fdtemplatelayoutcell.h>

1
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) {
        // 配置 cell 的數據源,和 "cellForRow" 乾的事一致,好比:
        cell.entity = self.feedEntities[indexPath.row];
    }];
}

 

寫完上面的代碼後,你就已經使用到了:

  • 和每一個 UITableViewCell ReuseID 一一對應的 template layout cell
    這個 cell 只爲了參加高度計算,不會真的顯示到屏幕上;它經過 UITableView 的 -dequeueCellForReuseIdentifier: 方法 lazy 建立並保存,因此要求這個 ReuseID 必須已經被註冊到了 UITableView 中,也就是說,要麼是 Storyboard 中的原型 cell,要麼就是使用了 UITableView 的 -registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的註冊方法。
  • 根據 autolayout 約束自動計算高度
    使用了系統在 iOS6 就提供的 API:-systemLayoutSizeFittingSize:
  • 根據 index path 的一套高度緩存機制
    計算出的高度會自動進行緩存,因此滑動時每一個 cell 真正的高度計算只會發生一次,後面的高度詢問都會命中緩存,減小了很是可觀的多餘計算。
  • 自動的緩存失效機制
    無須擔憂你數據源的變化引發的緩存失效,當調用如-reloadData,-deleteRowsAtIndexPaths:withRowAnimation:等任何一個觸發 UITableView 刷新機制的方法時,已有的高度緩存將以最小的代價執行失效。如刪除一個 indexPath 爲 [0:5] 的 cell 時,[0:0] ~ [0:4] 的高度緩存不受影響,而 [0:5] 後面全部的緩存值都向前移動一個位置。自動緩存失效機制對 UITableView 的 9 個公有 API 都進行了分別的處理,以保證沒有一次多餘的高度計算。
  • 預緩存機制
    預緩存機制將在 UITableView 沒有滑動的空閒時刻執行,計算和緩存那些尚未顯示到屏幕中的 cell,整個緩存過程徹底沒有感知,這使得完整列表的高度計算既沒有發生在加載時,又沒有發生在滑動時,同時保證了加載速度和滑動流暢性,下文會着重講下這塊的實現原理。

開始使用UITableView+FDTemplateLayoutCell

若是你以爲這個工具能幫獲得你,整合到工程也十分簡單。
使用 cocoapods:

1
pod search UITableView+FDTemplateLayoutCell

 

歡迎使用和支持這個工具,有 bug 請隨時反饋哦~
再複習下 github 地址: https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

相關文章
相關標籤/搜索