文章出處: 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ì)算方式,分別是:
- Auto Layout with UILabel in UITableViewCell
- Auto Layout with UITextView in UITableViewCell
- Manual Layout with UILabel in UITableViewCell
- Manual Layout with UITextView in UITableViewCell
- 隨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