1. 程式人生 > >iOS 8 自適應 Cell

iOS 8 自適應 Cell

在使用 table view 的時侯經常會遇到這樣的需求:table view 的 cell 中的內容是動態的,導致在開發的時候不知道一個 cell 的高度具體是多少,所以需要提供一個計算 cell 高度的演算法,在每次載入到這個 cell 的時候計算出 cell 真正的高度。

在 iOS 8 之前

沒有使用 Autolayout 的情況下,需要實現 table view delegate 的tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat方法,在這個方法中計算並返回 cell 的高度。比如,我有一個可以顯示任意行數的純文字 cell,計算 cell 的程式碼可以是這樣:

Objective-C
1234567891011121314 override func tableView(tableView: UITableView,heightForRowAtIndexPath indexPath: NSIndexPath)->CGFloat{let content=self.datas[indexPath.row] asStringlet padding: CGFloat=20let width=tableView.frame.size.width-padding *2;let size=CGSizeMake
(width,CGFloat.max)let attributes=[NSFontAttributeName: UIFont(name:"Helvetica", size:14)!]    letframe=content.boundingRectWithSize(size,        options: NSStringDrawingOptions.UsesLineFragmentOrigin,        attributes: attributes,        context: nil)returnframe.size.height+1;}

上面的程式碼是一個最簡單的例子,這個例子看起來好像沒有什麼問題。但是通過檢視這個 delegate 方法的文件後,可以知道,在每次 reload tableview 的時候,程式會先計算出每一個 cell 的高度,等所有高度計算完畢,確定了 tableview 的總的高度後,才開始渲染檢視並顯示在螢幕上。這意味著在顯示 table view 之前需要執行一堆的計算,並且這是在主執行緒中進行的,如果計算量太大程式就很有可能出現卡頓感。比如: table view 的資料有上千條,或者計算高度的程式碼中還要先獲取圖片再根據圖片計算高度,這些操作都是非常慢的。

如果在 cell 中使用了 autolayout,在計算 cell 高度時會更麻煩。有興趣的可以看這裡有篇關於如何在 autolayout 下動態計算高度 的文章。

為什麼不能等滾動到某個 cell 的時候,再呼叫計算這個 cell 高度的 delegate 呢?原因是 tableview 需要獲得它的內容的總高度,用這個高度去確定滾動條的大小等。直到 iOS 7 UITableViewDelegate中添加了新的 API

Objective-C
1 tableView(tableView: UITableView,estimatedHeightForRowAtIndexPathindexPath: NSIndexPath)->CGFloat

這個方法用於返回一個 cell 的預估高度,如果在程式中實現了這個方法,tableview 首次載入的時候就不會呼叫heightForRowAtIndexPath 方法,而是用estimatedHeightForRowAtIndexPath 返回的預估高度計算 tableview 的總高度,然後 tableview 就可以顯示出來了,等到 cell 可見的時候,再去呼叫heightForRowAtIndexPath 獲取 cell 的正確高度。

通過使用estimatedHeightForRowAtIndexPath 這個 Delegate 方法,解決了首次載入 table view 出現的效能問題。但還有一個麻煩的問題,就是在 cell 沒有被載入的時候計算 cell 的高度,上面給出的程式碼中,僅僅是計算一個 NSString 的高度,就需要不少程式碼了。這種計算實際上是必須的,然而在 iOS 8 開始,你可能可以不用再寫這些煩人的計算程式碼了!

iOS 8 的魔法

在 iOS 8 中,self size cell 提供了這樣一種機制:cell 如果有一個確定的寬度/高度,autolayout 會自動根據 cell 中的內容計算出對應的高度/寬度。

TableView 中的 cell 自適應

要讓 table view 的 cell 自適應內容,有幾個要點:

  • 設定的 AutoLayout 約束必須讓 cell 的 contentView 知道如何自動延展。關鍵點是 contentView 的 4 個邊都要設定連線到內容的約束,並且內容是會動態改變尺寸的。
  • UITableView 的 rowHeight 的值要設定為 UITableViewAutomaticDimension
  • 和 iOS 7 一樣,可以實現 estimatedHeightForRowAtIndexPath 方法提升 table view 的第一次載入速度。
  • 任何時候 cell 的 intrinsicContentSize 改變了(比如 table view 的寬度變了),都必須重新載入 table view 以更新 cell。

例子

在 Xcode 中新建一個專案,在 storyboard 中建立一個 UITableViewController 的 IB,建立一個如下樣子的 cell:

2014-11-13-self-size-cell-1

圖1 cell 外觀

這個 cell 中有 3 個元素,其中 imageView 的 autoLayout 約束為:

  • imageView 左邊離 contentView 左邊 0
  • imageView 上邊離 contentView 上邊 0
  • imageView 的 width 和 height 為 80
  • imageView 下邊離 contentView 下邊大於等於 0(為了防止內容太少,導致 cell 高度小於圖片高度)

titleLabel 的 autoLayout 約束為:

  • titleLabel 左邊離 imageView 右邊 8
  • titleLabel 上邊和 imageView 上邊在同一只線上
  • titleLabel 右邊離 contentView 右邊 0
  • titleLabel 下邊離 description 上邊 8
  • titleLabel 的高度小於等於 22,優先順序為 250

descriptionLabel 的約束為:

  • descriptionLabel 左邊和 titleLabel 左邊在同一直線上
  • descriptionLabel 上邊裡 titleLabel 8
  • descriptionLabel 下邊裡 contentView 下邊 0
  • descriptionLabel 右邊離 contentView 右邊 0

然後在這個 IB 對應的 UITableViewController 中載入一些資料進去,顯示效果如圖:

圖2 自適應 cell 效果圖

實現這個效果,我除了設定了 autoLayout,還設定了 tableView 的rowHeight = UITableViewAutomaticDimension,然後就是這樣了。一點計算 cell 高度的程式碼都沒有!!我連 heightForRowAtIndexPath都不用實現,真的是….爽出味啊!所以如果已經在開發 iOS 8 Only 的應用了一定要用autolayout,把煩人的計算交給 autolayout 去吧。

CollectionView 中的 cell 自適應

在 collection view 中也能讓 cell 自適應內容大小,如果 UICollectionView 的 layout 是一個 UICollectionViewFlowLayout,只需要將 layout.itemSize = ... 改成layout.estimatedItemSize = ...。 只要設定了 layout 的 estimatedItemSize,collection view 就會根據 cell 裡面的 autolayout 約束去確定cell 的大小。

原理:

  1. collection view 根據 layout 的 estimatedItemSize 算出估計的 contentSize,有了 contentSize collection view 就開始顯示
  2. collection view 在顯示的過程中,即將被顯示的 cell 根據 autolayout 的約束算出自適應內容的 size
  3. layout 從 collection view 裡獲取更新過的 size attribute
  4. layout 返回最終的 size attribute 給 collection view
  5. collection 使用這個最終的 size attribute 展示 cell

總結

這次 iOS 8 的釋出對 UI 開發來說是越來方便了,很多以前需要寫大量計算的程式碼現在都可以通過拖拖 IB 上的 UI 控制元件就可以實現了,當然首先你要會 autolayout。 如果很幸運的在開發 iOS 8 only 的應用,真的可以刪除heightForRowAtIndexPath中那些繁重的計算程式碼了!讓 autolayout 幫我們完成所有的工作吧。

參考