天天看點

動态計算UITableViewCell高度詳解 (轉)

感覺挺有用的一篇文章,分析了4種解決方案。回頭測試之。如果有别的方案,我會在後面補上。

原文位址:http://www.ifun.cc/blog/2014/02/21/dong-tai-ji-suan-uitableviewcellgao-du-xiang-jie/

不知道大家有沒有發現,在iOS APP開發過程中,UITableView是我們顯示内容常見的控件,本人覺得它是UIKit中最複雜的一個控件。今天要向大家介紹的就是如何動态計算UITableViewCell高度的一經驗與技巧,在此做一些總結友善朋友們查閱。

同時也歡迎廣大iOS技術人員加入技術開發群:<瘋狂IT人>93916004,衆人拾柴火焰高,大家一起讨論研究。

為了不讓講解空洞抽象,我還是用代碼執行個體的方式進行講解,這樣更容易接收與學習。

本文将介紹四種情況下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高度
由于隻是一個demo,是以命名這些都是随意從簡。

首先建立一個Single Page的工程,我命名為CellHeightDemo

1. Auto Layout with UILabel in UITableViewCell

建立一個空的xib,命名為C1.xib, 然後拖入一個UITableViewCell控件。接着建立一個UITableViewCell的子類,命名為C1類。然後在C1.xib中,将與C1類進行關聯。别給我說你不會關聯,如果不會那看下圖你就明白了。V^

動态計算UITableViewCell高度詳解 (轉)

隻需要在Class那裡寫入關聯的類名C1即可。

還有由于UITableViewCell需要重用功能,是以我們還需要設定一個重用辨別

動态計算UITableViewCell高度詳解 (轉)

在Identifier那裡寫入重用辨別C1,當然你也可以用任意字元。不過後面代碼裡需要這個字元。

接着我們來布局。用到了auto layout, 在此我不想介紹auto layout, 以後有時間再專門介紹,下圖就是我布局

動态計算UITableViewCell高度詳解 (轉)

這兒有兩點需要說明:1. UILabel的屬性Lines這兒設為了0表示顯示多行。2. Auto Layout一定要建立完完整。

接着我們在UITableView中來使用我們自定義的UITableViewCell C1.

首先我們建立一個UITableViewController的子類T1ViewController, 接着在Main.storyboard中拖入一個UITableViewController,并關聯T1ViewController.

動态計算UITableViewCell高度詳解 (轉)

一切都準備好了,那我們現在來寫點代碼,給UITableView加點料。

我們想要我們的UITableView使用C1.xib中自定義的Cell,那麼我們需要向UITableView進行注冊。

1
2
      
UINib *cellNib = [UINib nibWithNibName:@"C1" bundle:nil];
[self.tableView registerNib:cellNib forCellReuseIdentifier:@"C1"];
           

這樣就進行注冊了,接着我們還需要每行顯示的資料,為了簡單一點,我就聲明了一個NSArray變量來存放資料。

現在實作UITableViewDataSource的protocol:

1
2
3
4
5
6
7
8
9
10
11
12
      
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return self.tableData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    C1 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C1"];
    cell.t.text = [self.tableData objectAtIndex:indexPath.row];
    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高度的執行個體變量:

然後初始化它:

下面是計算Cell高度的實作:

1
2
3
4
5
6
7
      
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    C1 *cell = (C1 *)self.prototypeCell;
    cell.t.text = [self.tableData objectAtIndex:indexPath.row];
    CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    NSLog(@"h=%f", size.height + 1);
    return 1  + size.height;
}
           

看了代碼,可能你有點疑問,為何這兒要加1呢?筆者告訴你,如果不加1,結果就是錯誤的,Cell中UILabel将顯示不正确。原因就是因為這行代碼CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];由于是在cell.contentView上調用這個方法,那麼傳回的值将是contentView的高度,UITableViewCell的高度要比它的contentView要高1,也就是它的分隔線的高度。如果你不相信,那請看C1.xib的屬性,比較下面兩張圖。

動态計算UITableViewCell高度詳解 (轉)
動态計算UITableViewCell高度詳解 (轉)

發現沒Cell的高度是127, 面contentView的高度是126, 這下明白了吧。

為了讓讀者看清楚,我将Cell中UILabel的背景色充為了light gray.下面是運作效果:

動态計算UITableViewCell高度詳解 (轉)

2. Auto Layout with UITextView in UITableViewCell

本小段教程将介紹UITextView在cell中計算高度需要注意的地方。同樣參考上面我們建立一個C2.xib, UITableViewCell的子類C2,并關聯C2.xib與C2類。并在C2.xib中對其布局,同樣使用了auto layout. 布局如下圖:

動态計算UITableViewCell高度詳解 (轉)

創始UITableViewController的了類T2ViewController,在Main.storyboard中拖入UITableViewController,并關聯他們。接着代碼中注冊C2.xib到UITableView.

下面計是計算高度的代碼:

1
2
3
4
5
6
7
8
9
10
      
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    C2 *cell = (C2 *)self.prototypeCell;
    cell.t.text = [self.tableData objectAtIndex:indexPath.row];
    CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    CGSize textViewSize = [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)];
    CGFloat h = size.height + textViewSize.height;
    h = h > 89 ? h : 89;  //89是圖檔顯示的最低高度, 見xib
    NSLog(@"h=%f", h);
    return 1 + h;
}
           

在這兒我們是通過sizeThatFits:計算的UITextView的高度(這是計算UITextView内容全部顯示時的方法,在第四小段中我們還會用到它),然後加上systemLayoutSizeFittingSize:傳回的高度。為什麼要這樣呢? 因為UITextView内容的高度不會影響systemLayoutSizeFittingSize計算。這句話什麼意思呢?我真不知道如何用言語表達了。還是先上一張圖吧:

動态計算UITableViewCell高度詳解 (轉)

此圖中距頂的限制是10, 距底的限制8, 距左邊限制是87,距右邊的限制是13, 那麼systemLayoutSizeFittingSize:傳回的CGSize為height等于19, size等于100. 它UITextView的frame是不影響systemLayoutSizeFittingSize:的計算。不知道這樣說大家明白沒。

是以,我們需要加上textViewSize.height. 

下面是運作效果:

動态計算UITableViewCell高度詳解 (轉)

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
動态計算UITableViewCell高度詳解 (轉)

直接上代碼了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    C3 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C3"];
    cell.t.text = [self.tableData objectAtIndex:indexPath.row];
    [cell.t sizeToFit];
    return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    C3 *cell = (C3 *)self.prototypeCell;
    NSString *str = [self.tableData objectAtIndex:indexPath.row];
    cell.t.text = str;
    CGSize s = [str calculateSize:CGSizeMake(cell.t.frame.size.width, FLT_MAX) font:cell.t.font];
    CGFloat defaultHeight = cell.contentView.frame.size.height;
    CGFloat height = s.height > defaultHeight ? s.height : defaultHeight;
    NSLog(@"h=%f", height);
    return 1  + height;
}
           

這兒用到了一個NSString的Cagetory方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      
- (CGSize)calculateSize:(CGSize)size font:(UIFont *)font {
    CGSize expectedLabelSize = CGSizeZero;
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
        NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
        paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
        NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle.copy};
        expectedLabelSize = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
    }
    else {
        expectedLabelSize = [self sizeWithFont:font
                                       constrainedToSize:size
                                           lineBreakMode:NSLineBreakByWordWrapping];
    }
    return CGSizeMake(ceil(expectedLabelSize.width), ceil(expectedLabelSize.height));
}
           

原理上面我已說了,這兒沒有什麼好說明的,代碼一目了然。

運作效果如圖:

動态計算UITableViewCell高度詳解 (轉)

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
動态計算UITableViewCell高度詳解 (轉)

也直接上代碼了,直覺明了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    C4 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C4"];
    cell.t.text = [self.tableData objectAtIndex:indexPath.row];
    [cell.t sizeToFit];
    return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    C4 *cell = (C4 *)self.prototypeCell;
    NSString *str = [self.tableData objectAtIndex:indexPath.row];
    cell.t.text = str;
    CGSize s =  [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)];
    CGFloat defaultHeight = cell.contentView.frame.size.height;
    CGFloat height = s.height > defaultHeight ? s.height : defaultHeight;
    return 1  + height;
}
           

運作效果:

動态計算UITableViewCell高度詳解 (轉)

5.随UITextView高度動态改變Cell高度

本小節要介紹的一個功能是,UITextView中UITableViewCell中,當輸入UITextView中的字變多/變少時,高度變化,Cell高度與随之變化的功能。

按照前面介紹的,我們需要建立C5.xib, C5類, T5ViewController類,Main.storyboard中拖入UITableViewController,并分别建立關聯。 為了簡單,C5.xib中我就不加padding之類的了,如圖

記得開啟C5.xib的auto layout
動态計算UITableViewCell高度詳解 (轉)

先看代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
      
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    C5 *cell = [self.tableView dequeueReusableCellWithIdentifier:@"C5"];
    cell.t.text = @"123";
    cell.t.delegate = self;
    return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    C5 *cell = (C5 *)self.prototypeCell;
    cell.t.text = self.updatedStr;
    CGSize s =  [cell.t sizeThatFits:CGSizeMake(cell.t.frame.size.width, FLT_MAX)];
    CGFloat defaultHeight = cell.contentView.frame.size.height;
    CGFloat height = s.height > defaultHeight ? s.height : defaultHeight;
    return 1  + height;
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    if ([text isEqualToString:@"\n"]) {
        NSLog(@"h=%f", textView.contentSize.height);
    }
    return YES;
}
- (void)textViewDidChange:(UITextView *)textView {
    self.updatedStr = textView.text;
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
}

           

原理就是UITextView内容改變的時候,計算自身高度,然後通知UITableView更新,這樣就會觸發UITableViewCell高度重新計算,以達到目的。 

本文隻是簡單的介紹了一些原理與技巧,細節之處還請參看源碼 

時間倉促,難免有不少錯誤,還往指正。若有問題,請留言或加入QQ技術群:<瘋狂IT人>93916004

參考:

http://www.howlin-interactive.com/2013/01/creating-a-self-sizing-uitextview-within-a-uitableviewcell-in-ios-6/ 

http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights 

http://technet.weblineindia.com/mobile/add-auto-layout-support-for-uiscrollview-with-example-in-ios-app-development/ 

http://useyourloaf.com/blog/2014/02/14/table-view-cells-with-varying-row-heights.html

轉載于:https://www.cnblogs.com/JuneWang/p/3769078.html