轉:動態計算UITableViewCell高度詳解

 
不知道你們有沒有發現,在iOS APP開發過程當中,UITableView是咱們顯示內容常見的控件,本人以爲它是UIKit中最複雜的一個控件。今天要向你們介紹的就是如何動態計算UITableViewCell高度的一經驗與技巧,在此作一些總結方便朋友們查閱。爲了避免讓講解空洞抽象,我仍是用代碼實例的方式進行講解,這樣更容易接收與學習。
 
本文將介紹四種狀況下UITableViewCell的計算方式,分別是:
1. Auto Layout with UILabel in UITableViewCell
2. Auto Layout with UITextView in UITableViewCell
3. Manual Layout with UILabel in UITableViewCell
4. Manual Layout with UITextView in UITableViewCell
5. 隨UITextView高度動態改變Cell高度
 
首先建立一個Single Page的工程,我命名爲CellHeightDemo
 
1. Auto Layout with UILabel in UITableViewCell
建立一個空的xib,命名爲C1.xib, 而後拖入一個UITableViewCell控件。接着建立一個UITableViewCell的子類,命名爲C1類。而後在C1.xib中,將與C1類進行關聯。別給我說你不會關聯,若是不會那看下圖你就明白了。 
 
只須要在Class那裏寫入關聯的類名C1便可。
 
還有因爲UITableViewCell須要重用功能,因此咱們還須要設置一個重用標識 
 
在Identifier那裏寫入重用標識C1,固然你也能夠用任意字符。不事後面代碼裏須要這個字符。
 
接着咱們來佈局。用到了auto layout, 在此我不想介紹auto layout, 之後有時間再專門介紹,下圖就是我佈局 
 
這兒有兩點須要說明:1. UILabel的屬性Lines這兒設爲了0表示顯示多行。2. Auto Layout必定要創建完完整。
 
接着咱們在UITableView中來使用咱們自定義的UITableViewCell C1。
 
首先咱們建立一個UITableViewController的子類T1ViewController, 接着在Main.storyboard中拖入一個UITableViewController,並關聯T1ViewController。
 
一切都準備好了,那咱們如今來寫點代碼,給UITableView加點料。
 
咱們想要咱們的UITableView使用C1.xib中自定義的Cell,那麼咱們須要向UITableView進行註冊。
  1. UINib *cellNib = [UINib nibWithNibName:@"C1" bundle:nil]; 
  2. [self.tableView registerNib:cellNib forCellReuseIdentifier:@"C1"]; 
 
這樣就進行註冊了,接着咱們還須要每行顯示的數據,爲了簡單一點,我就聲明瞭一個NSArray變量來存放數據。
  1. self.tableData = @[@"1\n2\n3\n4\n5\n6", @"123456789012345678901234567890", @"1\n2", @"1\n2\n3", @"1"]; 
 
如今實現UITableViewDataSource的protocol:
  1. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
  2.     // Return the number of rows in the section. 
  3.     return self.tableData.count; 
  4.   
  5. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
  6.     C1 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C1"]; 
  7.     cell.t.text = [self.tableData objectAtIndex:indexPath.row]; 
  8.     return cell; 
 
從self.tableData中的數據咱們能夠看到,每個Cell顯示的數據高度是不同的,那麼咱們須要動態計算Cell的高度。因爲是auto layout,因此咱們須要用到一個新的API systemLayoutSizeFittingSize:來計算UITableViewCell所佔空間高度。Cell的高度是在- (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath這個UITableViewDelegate的方法裏面傳給UITableView的。
 
這裏有一個須要特別注意的問題,也是效率問題。UITableView是一次性計算完全部Cell的高度,若是有1W個Cell,那麼- (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath就會觸發1W次,而後才顯示內容。不過在iOS7之後,提供了一個新方法能夠避免這1W次調用,它就是- (CGFloat)tableView:(UITableView )tableView estimatedHeightForRowAtIndexPath:(NSIndexPath )indexPath。要求返回一個Cell的估計值,實現了這個方法,那只有顯示的Cell纔會觸發計算高度的protocol. 因爲systemLayoutSizeFittingSize須要cell的一個實例才能計算,因此這兒用一個成員變量存一個Cell的實列,這樣就不須要每次計算Cell高度的時候去動態生成一個Cell實例,這樣即方便也高效也少用內存,可謂一舉三得。
 
咱們聲明一個存計算Cell高度的實例變量:
  1. @property (nonatomic, strong) UITableViewCell *prototypeCell; 
 
而後初始化它:
  1. self.prototypeCell  = [self.tableView dequeueReusableCellWithIdentifier:@"C1"]; 
 
下面是計算Cell高度的實現:
  1. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
  2.     C1 *cell = (C1 *)self.prototypeCell; 
  3.     cell.t.text = [self.tableData objectAtIndex:indexPath.row]; 
  4.     CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; 
  5.     NSLog(@"h=%f", size.height + 1); 
  6.     return 1  + size.height; 
 
看了代碼,可能你有點疑問,爲什麼這兒要加1呢?筆者告訴你,若是不加1,結果就是錯誤的,Cell中UILabel將顯示不正確。緣由就是由於這行代碼CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];因爲是在cell.contentView上調用這個方法,那麼返回的值將是contentView的高度,UITableViewCell的高度要比它的contentView要高1,也就是它的分隔線的高度。若是你不相信,那請看C1.xib的屬性,比較下面兩張圖。 
 
 
發現沒Cell的高度是127, 面contentView的高度是126, 這下明白了吧。
 
爲了讓讀者看清楚,我將Cell中UILabel的背景色充爲了light gray.下面是運行效果: 
 
2. Auto Layout with UITextView in UITableViewCell
本小段教程將介紹UITextView在cell中計算高度須要注意的地方。一樣參考上面咱們建立一個C2.xib, UITableViewCell的子類C2,並關聯C2.xib與C2類。並在C2.xib中對其佈局,一樣使用了auto layout. 佈局以下圖: 
 
創始UITableViewController的了類T2ViewController,在Main.storyboard中拖入UITableViewController,並關聯他們。接着代碼中註冊C2.xib到UITableView.
 
下面計是計算高度的代碼:
  1. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
  2.     C2 *cell = (C2 *)self.prototypeCell; 
  3.     cell.t.text = [self.tableData objectAtIndex:indexPath.row]; 
  4.     CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; 
  5.     CGSize textViewSize = [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)]; 
  6.     CGFloat h = size.height + textViewSize.height; 
  7.     h = h > 89 ? h : 89;  //89是圖片顯示的最低高度, 見xib 
  8.     NSLog(@"h=%f", h); 
  9.     return 1 + h; 
 
在這兒咱們是經過sizeThatFits:計算的UITextView的高度(這是計算UITextView內容所有顯示時的方法,在第四小段中咱們還會用到它),而後加上systemLayoutSizeFittingSize:返回的高度。爲何要這樣呢? 由於UITextView內容的高度不會影響systemLayoutSizeFittingSize計算。這句話什麼意思呢?我真不知道如何用言語表達了。仍是先上一張圖吧:
   
此圖中距頂的約束是10, 距底的約束8, 距左邊約束是87,距右邊的約束是13, 那麼systemLayoutSizeFittingSize:返回的CGSize爲height等於19, size等於100. 它UITextView的frame是不影響systemLayoutSizeFittingSize:的計算。不知道這樣說你們明白沒。
因此,咱們須要加上textViewSize.height. 
 
下面是運行效果: 
 
3. Manual Layout with UILabel in UITableViewCell
本小段教程將介紹UILabel在Manual layout cell中計算高度, 原理是根據字體與字符串長度來計算長度與寬度。 按照前面介紹的,咱們須要建立C3.xib, C3類, T3ViewController類,Main.storyboard中拖入UITableViewController,並分別創建關聯。 爲了簡單,C3.xib中我就不加padding之類的了,如圖
 
記得關閉C3.xib的auto layout 
 
直接上代碼了:
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
  2.     C3 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C3"]; 
  3.     cell.t.text = [self.tableData objectAtIndex:indexPath.row]; 
  4.     [cell.t sizeToFit]; 
  5.     return cell; 
  6.   
  7. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
  8.     C3 *cell = (C3 *)self.prototypeCell; 
  9.     NSString *str = [self.tableData objectAtIndex:indexPath.row]; 
  10.     cell.t.text = str; 
  11.     CGSize s = [str calculateSize:CGSizeMake(cell.t.frame.size.width, FLT_MAX) font:cell.t.font]; 
  12.     CGFloat defaultHeight = cell.contentView.frame.size.height; 
  13.     CGFloat height = s.height > defaultHeight ? s.height : defaultHeight; 
  14.     NSLog(@"h=%f", height); 
  15.     return 1  + height; 
 
這兒用到了一個NSString的Cagetory方法:
  1. - (CGSize)calculateSize:(CGSize)size font:(UIFont *)font { 
  2.     CGSize expectedLabelSize = CGSizeZero; 
  3.      
  4.     if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) { 
  5.         NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 
  6.         paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; 
  7.         NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle.copy}; 
  8.          
  9.         expectedLabelSize = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size; 
  10.     } 
  11.     else { 
  12.         expectedLabelSize = [self sizeWithFont:font 
  13.                                        constrainedToSize:size 
  14.                                            lineBreakMode:NSLineBreakByWordWrapping]; 
  15.     } 
  16.   
  17.     return CGSizeMake(ceil(expectedLabelSize.width), ceil(expectedLabelSize.height)); 
 
原理上面我已說了,這兒沒有什麼好說明的,代碼一目瞭然。
 
運行效果如圖: 
 
4. Manual Layout with UITextView in UITableViewCell
本小段教程將介紹UITextView在Manual layout cell中計算高度, 原理是與第二小節裏的相同,用sizeThatFits:的方法計算UITextView的長度與高度。而後加上padding就是Cell的高度。 按照前面介紹的,咱們須要建立C4.xib, C4類, T4ViewController類,Main.storyboard中拖入UITableViewController,並分別創建關聯。 爲了簡單,C4.xib中我就不加padding之類的了,如圖
 
記得關閉C4.xib的auto layout 
 
也直接上代碼了,直觀明瞭:
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
  2.     C4 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C4"]; 
  3.     cell.t.text = [self.tableData objectAtIndex:indexPath.row]; 
  4.     [cell.t sizeToFit]; 
  5.     return cell; 
  6.   
  7. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
  8.     C4 *cell = (C4 *)self.prototypeCell; 
  9.     NSString *str = [self.tableData objectAtIndex:indexPath.row]; 
  10.     cell.t.text = str; 
  11.     CGSize s =  [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)]; 
  12.     CGFloat defaultHeight = cell.contentView.frame.size.height; 
  13.     CGFloat height = s.height > defaultHeight ? s.height : defaultHeight; 
  14.     return 1  + height; 
  15.   
 
運行效果: 
 
5. 隨UITextView高度動態改變Cell高度
本小節要介紹的一個功能是,UITextView中UITableViewCell中,當輸入UITextView中的字變多/變少時,高度變化,Cell高度與隨之變化的功能。
按照前面介紹的,咱們須要建立C5.xib, C5類, T5ViewController類,Main.storyboard中拖入UITableViewController,並分別創建關聯。 爲了簡單,C5.xib中我就不加padding之類的了,如圖
 
記得開啓C5.xib的auto layout 
 
先看代碼:
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
  2.     C5 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C5"]; 
  3.     cell.t.text = @"123"; 
  4.     cell.t.delegate = self; 
  5.     return cell; 
  6.  
  7. #pragma mark - UITableViewDelegate 
  8. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
  9.     C5 *cell = (C5 *)self.prototypeCell; 
  10.     cell.t.text = self.updatedStr; 
  11.     CGSize s =  [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)]; 
  12.     CGFloat defaultHeight = cell.contentView.frame.size.height; 
  13.     CGFloat height = s.height > defaultHeight ? s.height : defaultHeight; 
  14.     return 1  + height; 
  15.  
  16. #pragma mark - UITextViewDelegate 
  17. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { 
  18.     if ([text isEqualToString:@"\n"]) { 
  19.         NSLog(@"h=%f", textView.contentSize.height); 
  20.     } 
  21.     return YES; 
  22.  
  23. - (void)textViewDidChange:(UITextView *)textView { 
  24.     self.updatedStr = textView.text; 
  25.     [self.tableView beginUpdates]; 
  26.     [self.tableView endUpdates]; 
 
原理就是UITextView內容改變的時候,計算自身高度,而後通知UITableView更新,這樣就會觸發UITableViewCell高度從新計算,以達到目的。 
 
本文只是簡單的介紹了一些原理與技巧,細節之處還請參看 源碼 
 
參考:
  
 
 
相關文章
相關標籤/搜索