Layout cell 自適應(yīng)高度

文章出處: http://www.cocoachina.com/industry/20140604/8668.html

不知道大家有沒有發(fā)現(xiàn),在iOS APP開發(fā)過程中,UITableView是我們顯示內(nèi)容常見的控件,本人覺得它是UIKit中最復(fù)雜的一個(gè)控件。今天要向大家介紹的就是如何動(dòng)態(tài)計(jì)算UITableViewCell高度的一經(jīng)驗(yàn)與技巧,在此做一些總結(jié)方便朋友們查閱。為了不讓講解空洞抽象,我還是用代碼實(shí)例的方式進(jìn)行講解,這樣更容易接收與學(xué)習(xí)。

本文將介紹四種情況下UITableViewCell的計(jì)算方式,分別是:

  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高度動(dòng)態(tài)改變Cell高度

首先創(chuàng)建一個(gè)Single Page的工程,我命名為CellHeightDemo

1. Auto Layout with UILabel in UITableViewCell
創(chuàng)建一個(gè)空的xib,命名為C1.xib, 然后拖入一個(gè)UITableViewCell控件。接著創(chuàng)建一個(gè)UITableViewCell的子類,命名為C1類。然后在C1.xib中,將與C1類進(jìn)行關(guān)聯(lián)。別給我說你不會(huì)關(guān)聯(lián),如果不會(huì)那看下圖你就明白了。

只需要在Class那里寫入關(guān)聯(lián)的類名C1即可。

還有由于UITableViewCell需要重用功能,所以我們還需要設(shè)置一個(gè)重用標(biāo)識(shí)


在Identifier那里寫入重用標(biāo)識(shí)C1,當(dāng)然你也可以用任意字符。不過后面代碼里需要這個(gè)字符。

接著我們來布局。用到了auto layout, 在此我不想介紹auto layout, 以后有時(shí)間再專門介紹,下圖就是我布局


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

接著我們?cè)赨ITableView中來使用我們自定義的UITableViewCell C1。

首先我們創(chuàng)建一個(gè)UITableViewController的子類T1ViewController, 接著在Main.storyboard中拖入一個(gè)UITableViewController,并關(guān)聯(lián)T1ViewController。


一切都準(zhǔn)備好了,那我們現(xiàn)在來寫點(diǎn)代碼,給UITableView加點(diǎn)料。

我們想要我們的UITableView使用C1.xib中自定義的Cell,那么我們需要向UITableView進(jìn)行注冊(cè)。
UINib *cellNib = [UINib nibWithNibName:@"C1" bundle:nil];
[self.tableView registerNib:cellNib forCellReuseIdentifier:@"C1"];

這樣就進(jìn)行注冊(cè)了,接著我們還需要每行顯示的數(shù)據(jù),為了簡(jiǎn)單一點(diǎn),我就聲明了一個(gè)NSArray變量來存放數(shù)據(jù)。
self.tableData = @[@"1\n2\n3\n4\n5\n6", @"123456789012345678901234567890", @"1\n2", @"1\n2\n3", @"1"];

現(xiàn)在實(shí)現(xiàn)UITableViewDataSource的protocol:

  • (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中的數(shù)據(jù)我們可以看到,每一個(gè)Cell顯示的數(shù)據(jù)高度是不一樣的,那么我們需要?jiǎng)討B(tài)計(jì)算Cell的高度。由于是auto layout,所以我們需要用到一個(gè)新的API systemLayoutSizeFittingSize:來計(jì)算UITableViewCell所占空間高度。Cell的高度是在- (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath這個(gè)UITableViewDelegate的方法里面?zhèn)鹘oUITableView的。

這里有一個(gè)需要特別注意的問題,也是效率問題。UITableView是一次性計(jì)算完所有Cell的高度,如果有1W個(gè)Cell,那么- (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath就會(huì)觸發(fā)1W次,然后才顯示內(nèi)容。不過在iOS7以后,提供了一個(gè)新方法可以避免這1W次調(diào)用,它就是- (CGFloat)tableView:(UITableView )tableView estimatedHeightForRowAtIndexPath:(NSIndexPath )indexPath。要求返回一個(gè)Cell的估計(jì)值,實(shí)現(xiàn)了這個(gè)方法,那只有顯示的Cell才會(huì)觸發(fā)計(jì)算高度的protocol. 由于systemLayoutSizeFittingSize需要cell的一個(gè)實(shí)例才能計(jì)算,所以這兒用一個(gè)成員變量存一個(gè)Cell的實(shí)列,這樣就不需要每次計(jì)算Cell高度的時(shí)候去動(dòng)態(tài)生成一個(gè)Cell實(shí)例,這樣即方便也高效也少用內(nèi)存,可謂一舉三得。

我們聲明一個(gè)存計(jì)算Cell高度的實(shí)例變量:
@property (nonatomic, strong) UITableViewCell *prototypeCell;

然后初始化它:
self.prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:@"C1"];

下面是計(jì)算Cell高度的實(shí)現(xiàn):

  • (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;
    }

看了代碼,可能你有點(diǎn)疑問,為何這兒要加1呢?筆者告訴你,如果不加1,結(jié)果就是錯(cuò)誤的,Cell中UILabel將顯示不正確。原因就是因?yàn)檫@行代碼CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];由于是在cell.contentView上調(diào)用這個(gè)方法,那么返回的值將是contentView的高度,UITableViewCell的高度要比它的contentView要高1,也就是它的分隔線的高度。如果你不相信,那請(qǐng)看C1.xib的屬性,比較下面兩張圖。


發(fā)現(xiàn)沒Cell的高度是127, 面contentView的高度是126, 這下明白了吧。

為了讓讀者看清楚,我將Cell中UILabel的背景色充為了light gray.下面是運(yùn)行效果:


2. Auto Layout with UITextView in UITableViewCell
本小段教程將介紹UITextView在cell中計(jì)算高度需要注意的地方。同樣參考上面我們創(chuàng)建一個(gè)C2.xib, UITableViewCell的子類C2,并關(guān)聯(lián)C2.xib與C2類。并在C2.xib中對(duì)其布局,同樣使用了auto layout. 布局如下圖:

創(chuàng)始UITableViewController的了類T2ViewController,在Main.storyboard中拖入U(xiǎn)ITableViewController,并關(guān)聯(lián)他們。接著代碼中注冊(cè)C2.xib到UITableView.

下面計(jì)是計(jì)算高度的代碼:

  • (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:計(jì)算的UITextView的高度(這是計(jì)算UITextView內(nèi)容全部顯示時(shí)的方法,在第四小段中我們還會(huì)用到它),然后加上systemLayoutSizeFittingSize:返回的高度。為什么要這樣呢? 因?yàn)閁ITextView內(nèi)容的高度不會(huì)影響systemLayoutSizeFittingSize計(jì)算。這句話什么意思呢?我真不知道如何用言語表達(dá)了。還是先上一張圖吧:



此圖中距頂?shù)募s束是10, 距底的約束8, 距左邊約束是87,距右邊的約束是13, 那么systemLayoutSizeFittingSize:返回的CGSize為height等于19, size等于100. 它UITextView的frame是不影響systemLayoutSizeFittingSize:的計(jì)算。不知道這樣說大家明白沒。
所以,我們需要加上textViewSize.height.

下面是運(yùn)行效果:


3. Manual Layout with UILabel in UITableViewCell
本小段教程將介紹UILabel在Manual layout cell中計(jì)算高度, 原理是根據(jù)字體與字符串長(zhǎng)度來計(jì)算長(zhǎng)度與寬度。 按照前面介紹的,我們需要?jiǎng)?chuàng)建C3.xib, C3類, T3ViewController類,Main.storyboard中拖入U(xiǎn)ITableViewController,并分別建立關(guān)聯(lián)。 為了簡(jiǎn)單,C3.xib中我就不加padding之類的了,如圖

記得關(guān)閉C3.xib的auto layout


直接上代碼了:

  • (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;
    }

這兒用到了一個(gè)NSString的Cagetory方法:

  • (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));
    }

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

運(yùn)行效果如圖:


4. Manual Layout with UITextView in UITableViewCell
本小段教程將介紹UITextView在Manual layout cell中計(jì)算高度, 原理是與第二小節(jié)里的相同,用sizeThatFits:的方法計(jì)算UITextView的長(zhǎng)度與高度。然后加上padding就是Cell的高度。 按照前面介紹的,我們需要?jiǎng)?chuàng)建C4.xib, C4類, T4ViewController類,Main.storyboard中拖入U(xiǎn)ITableViewController,并分別建立關(guān)聯(lián)。 為了簡(jiǎn)單,C4.xib中我就不加padding之類的了,如圖

記得關(guān)閉C4.xib的auto layout


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

  • (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;
    }

運(yùn)行效果:


5. 隨UITextView高度動(dòng)態(tài)改變Cell高度
本小節(jié)要介紹的一個(gè)功能是,UITextView中UITableViewCell中,當(dāng)輸入U(xiǎn)ITextView中的字變多/變少時(shí),高度變化,Cell高度與隨之變化的功能。
按照前面介紹的,我們需要?jiǎng)?chuàng)建C5.xib, C5類, T5ViewController類,Main.storyboard中拖入U(xiǎn)ITableViewController,并分別建立關(guān)聯(lián)。 為了簡(jiǎn)單,C5.xib中我就不加padding之類的了,如圖

記得開啟C5.xib的auto layout


先看代碼:

  • (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內(nèi)容改變的時(shí)候,計(jì)算自身高度,然后通知UITableView更新,這樣就會(huì)觸發(fā)UITableViewCell高度重新計(jì)算,以達(dá)到目的。

本文只是簡(jiǎn)單的介紹了一些原理與技巧,細(xì)節(jié)之處還請(qǐng)參看源碼

參考:
Creating a Self-Sizing UITextView Within a UITableViewCell in iOS 6

Auto Layout for Table View Cells with Dynamic Heights

Add Auto Layout Support For UIScrollView with Example in iOS App Development

Table View Cells With Varying Row Heights

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,446評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,951評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,189評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,800評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,420評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,755評(píng)論 2 371

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