4 回答

TA貢獻(xiàn)1805條經(jīng)驗(yàn) 獲得超10個(gè)贊
TL; DR:不喜歡讀書嗎?直接跳轉(zhuǎn)到GitHub上的示例項(xiàng)目:
iOS 8示例項(xiàng)目 - 需要iOS 8
iOS 7示例項(xiàng)目 - 適用于iOS 7+
概念描述
無論您正在開發(fā)哪種iOS版本,以下前兩個(gè)步驟均適用。
1.設(shè)置和添加約束
在UITableViewCell
子類中,添加約束,以便單元格的子視圖的邊緣固定到單元格的contentView邊緣(最重要的是頂部和底部邊緣)。注意:不要將子視圖固定到單元格本身; 只到細(xì)胞的contentView
!通過確保每個(gè)子視圖的垂直維度中的內(nèi)容壓縮阻力和內(nèi)容擁抱約束不會(huì)被您添加的更高優(yōu)先級(jí)約束覆蓋,讓這些子視圖的內(nèi)在內(nèi)容大小驅(qū)動(dòng)表格視圖單元格內(nèi)容視圖的高度。(嗯?點(diǎn)擊這里。)
請(qǐng)記住,我們的想法是將單元格的子視圖垂直連接到單元格的內(nèi)容視圖,以便它們可以“施加壓力”并使內(nèi)容視圖擴(kuò)展以適應(yīng)它們。使用帶有幾個(gè)子視圖的示例單元格,這里是一個(gè)視覺圖示,說明您的約束的某些 (不是全部?。?/em>需要看起來像什么:
您可以想象,隨著更多文本被添加到上面示例單元格中的多行正文標(biāo)簽,它將需要垂直增長(zhǎng)以適合文本,這將有效地迫使單元格在高度上增長(zhǎng)。(當(dāng)然,您需要正確的約束才能使其正常工作?。?/p>
獲得正確的約束絕對(duì)是使用自動(dòng)布局獲得動(dòng)態(tài)單元格高度最困難和最重要的部分。如果你在這里犯了錯(cuò)誤,它可能會(huì)阻止其他一切工作 - 所以慢慢來!我建議在代碼中設(shè)置約束,因?yàn)槟_切地知道在哪里添加了哪些約束,并且在出現(xiàn)問題時(shí)更容易調(diào)試。在代碼中添加約束可能與使用布局錨點(diǎn)的Interface Builder或GitHub上可用的一個(gè)非常棒的開源API一樣簡(jiǎn)單并且功能強(qiáng)大得多。
如果要在代碼中添加約束,則應(yīng)該在
updateConstraints
UITableViewCell子類的方法中執(zhí)行此操作一次。請(qǐng)注意,updateConstraints
可能會(huì)多次調(diào)用,因此為了避免多次添加相同的約束,請(qǐng)確保updateConstraints
在檢查布爾屬性(例如,didSetupConstraints
在運(yùn)行約束后設(shè)置為YES)中包含約束添加代碼 - 添加代碼一次)。另一方面,如果您有更新現(xiàn)有約束的代碼(例如constant
在某些約束上調(diào)整屬性),請(qǐng)將其放在updateConstraints
檢查中但不在檢查之外,didSetupConstraints
以便每次調(diào)用方法時(shí)都可以運(yùn)行它。
2.確定唯一的表格視圖單元格重用標(biāo)識(shí)符
對(duì)于單元中每個(gè)唯一的約束集,請(qǐng)使用唯一的單元重用標(biāo)識(shí)符。換句話說,如果您的單元格具有多個(gè)唯一布局,則每個(gè)唯一布局應(yīng)接收其自己的重用標(biāo)識(shí)符。(當(dāng)您的單元格變體具有不同數(shù)量的子視圖,或者子視圖以不同的方式排列時(shí),您需要使用新的重用標(biāo)識(shí)符的良好提示。)
例如,如果您在每個(gè)單元格中顯示電子郵件,則可能有4種獨(dú)特的布局:僅包含主題的郵件,包含主題和正文的郵件,包含主題和照片附件的郵件以及包含主題的郵件,身體和照片附件。每個(gè)布局都有完全不同的約束來實(shí)現(xiàn)它,因此一旦初始化單元并為這些單元類型之一添加約束,單元應(yīng)該獲得特定于該單元類型的唯一重用標(biāo)識(shí)符。這意味著當(dāng)您將單元格出列以便重復(fù)使用時(shí),已經(jīng)添加了約束并準(zhǔn)備好使用該單元格類型。
請(qǐng)注意,由于內(nèi)在內(nèi)容大小的差異,具有相同約束(類型)的單元格可能仍然具有不同的高度!由于內(nèi)容的大小不同,不要將根本不同的布局(不同的約束)與不同的計(jì)算視圖幀(由相同的約束條件解決)混淆。
不要將具有完全不同約束集的單元添加到同一重用池(即使用相同的重用標(biāo)識(shí)符),然后嘗試刪除舊約束并在每次出列后從頭開始設(shè)置新約束。內(nèi)部自動(dòng)布局引擎不是為處理約束中的大規(guī)模更改而設(shè)計(jì)的,您將看到大量的性能問題。
適用于iOS 8 - Self-Sizing Cells
3.啟用行高估計(jì)
要啟用自調(diào)整大小的表視圖單元格,必須將表視圖的rowHeight屬性設(shè)置為UITableViewAutomaticDimension。您還必須為estimatedRowHeight屬性分配值。一旦設(shè)置了這兩個(gè)屬性,系統(tǒng)就會(huì)使用“自動(dòng)布局”來計(jì)算行的實(shí)際高度
Apple:使用自定義表格查看單元格
在iOS 8中,Apple已經(jīng)內(nèi)置了以前必須在iOS 8之前實(shí)現(xiàn)的大部分工作。為了使自定義單元機(jī)制能夠工作,必須首先將rowHeight
表視圖上的屬性設(shè)置為常量UITableViewAutomaticDimension
。然后,您只需通過將表視圖的estimatedRowHeight
屬性設(shè)置為非零值來啟用行高估計(jì),例如:
self.tableView.rowHeight = UITableViewAutomaticDimension;self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
這樣做是為表視圖提供臨時(shí)估計(jì)/占位符,用于尚未在屏幕上顯示的單元格的行高。然后,當(dāng)這些單元格即將在屏幕上滾動(dòng)時(shí),將計(jì)算實(shí)際行高。要確定每一行的實(shí)際高度,表視圖contentView
會(huì)根據(jù)內(nèi)容視圖的已知固定寬度(基于表視圖的寬度,減去任何其他內(nèi)容,如節(jié)索引)自動(dòng)詢問每個(gè)單元格需要的高度?;蚋郊晥D)以及已添加到單元格內(nèi)容視圖和子視圖中的自動(dòng)布局約束。確定此實(shí)際單元格高度后,將使用新的實(shí)際高度更新行的舊估計(jì)高度(并且根據(jù)需要對(duì)表視圖的contentSize / contentOffset進(jìn)行任何調(diào)整)。
一般來說,您提供的估計(jì)值不必非常準(zhǔn)確 - 它僅用于在表格視圖中正確調(diào)整滾動(dòng)指示器的大小,并且表格視圖可以很好地調(diào)整滾動(dòng)指示器以獲得不正確的估計(jì)值在屏幕上滾動(dòng)單元格。您應(yīng)該將estimatedRowHeight
表視圖(在viewDidLoad
或類似)中的屬性設(shè)置為常量值,即“平均”行高。只有當(dāng)您的行高具有極端可變性(例如,相差一個(gè)數(shù)量級(jí))并且您在滾動(dòng)時(shí)注意到滾動(dòng)指示符“跳躍”時(shí),您仍然tableView:estimatedHeightForRowAtIndexPath:
需要執(zhí)行所需的最小計(jì)算以返回對(duì)每行更準(zhǔn)確的估計(jì)。
對(duì)于iOS 7支持(自己實(shí)現(xiàn)自動(dòng)細(xì)胞大小調(diào)整)
3.進(jìn)行布局通過并獲取單元格高度
首先,實(shí)例化表視圖單元的屏幕外實(shí)例,每個(gè)重用標(biāo)識(shí)符的一個(gè)實(shí)例,嚴(yán)格用于高度計(jì)算。(屏幕外意味著單元格引用存儲(chǔ)在視圖控制器上的屬性/ ivar中,并且永遠(yuǎn)不會(huì)從tableView:cellForRowAtIndexPath:
表格視圖返回以實(shí)際在屏幕上呈現(xiàn)。)接下來,必須使用確切內(nèi)容(例如文本,圖像等)配置單元格如果要在表格視圖中顯示它將保持。
然后,迫使立即布局其子視圖的細(xì)胞,然后使用systemLayoutSizeFittingSize:
該方法UITableViewCell
的contentView
找出電池的必要高度是什么。使用UILayoutFittingCompressedSize
去適應(yīng)單元格中的所有內(nèi)容所需的最小尺寸。然后可以從tableView:heightForRowAtIndexPath:
委托方法返回高度。
4.使用估計(jì)行高
如果你的表視圖中有超過幾十行,你會(huì)發(fā)現(xiàn)在第一次加載表視圖時(shí),執(zhí)行自動(dòng)布局約束求解會(huì)很快使主線程陷入困境,就像在第一次加載時(shí)tableView:heightForRowAtIndexPath:
調(diào)用每一行一樣(為了計(jì)算滾動(dòng)指示器的大?。?/p>
從iOS 7開始,您可以(并且絕對(duì)應(yīng)該)estimatedRowHeight
在表視圖中使用該屬性。這樣做是為表視圖提供臨時(shí)估計(jì)/占位符,用于尚未在屏幕上顯示的單元格的行高。然后,當(dāng)這些單元格即將在屏幕上滾動(dòng)時(shí),將計(jì)算實(shí)際行高(通過調(diào)用tableView:heightForRowAtIndexPath:
),并使用實(shí)際行更新估計(jì)的高度。
一般來說,您提供的估計(jì)值不必非常準(zhǔn)確 - 它僅用于在表格視圖中正確調(diào)整滾動(dòng)指示器的大小,并且表格視圖可以很好地調(diào)整滾動(dòng)指示器以獲得不正確的估計(jì)值在屏幕上滾動(dòng)單元格。您應(yīng)該將estimatedRowHeight
表視圖(在viewDidLoad
或類似)中的屬性設(shè)置為常量值,即“平均”行高。只有當(dāng)您的行高具有極端可變性(例如,相差一個(gè)數(shù)量級(jí))并且您在滾動(dòng)時(shí)注意到滾動(dòng)指示符“跳躍”時(shí),您仍然tableView:estimatedHeightForRowAtIndexPath:
需要執(zhí)行所需的最小計(jì)算以返回對(duì)每行更準(zhǔn)確的估計(jì)。
5.(如果需要)添加行高度緩存
如果你已經(jīng)完成了上述所有工作,并且在進(jìn)行約束求解時(shí)仍然發(fā)現(xiàn)性能慢得令人無法接受tableView:heightForRowAtIndexPath:
,那么很遺憾,你需要為單元高度實(shí)現(xiàn)一些緩存。(這是Apple工程師建議的方法。)一般的想法是讓Auto Layout引擎第一次解決約束,然后緩存該單元格的計(jì)算高度,并將緩存值用于該單元格高度的所有未來請(qǐng)求。當(dāng)然,技巧是確保在發(fā)生任何可能導(dǎo)致單元格高度發(fā)生變化的情況時(shí)清除單元格的緩存高度 - 主要是當(dāng)單元格的內(nèi)容發(fā)生變化或其他重要事件發(fā)生時(shí)(如用戶調(diào)整)動(dòng)態(tài)類型文本大小滑塊)。
iOS 7通用示例代碼(有很多多汁的評(píng)論)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // Determine which reuse identifier should be used for the cell at this // index path, depending on the particular layout required (you may have // just one, or may have many). NSString *reuseIdentifier = ...; // Dequeue a cell for the reuse identifier. // Note that this method will init and return a new cell if there isn't // one available in the reuse pool, so either way after this line of // code you will have a cell with the correct constraints ready to go. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; // Configure the cell with content for the given indexPath, for example: // cell.textLabel.text = someTextForThisCell; // ... // Make sure the constraints have been set up for this cell, since it // may have just been created from scratch. Use the following lines, // assuming you are setting up constraints from within the cell's // updateConstraints method: [cell setNeedsUpdateConstraints]; [cell updateConstraintsIfNeeded]; // If you are using multi-line UILabels, don't forget that the // preferredMaxLayoutWidth needs to be set correctly. Do it at this // point if you are NOT doing it within the UITableViewCell subclass // -[layoutSubviews] method. For example: // cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds); return cell;}- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ // Determine which reuse identifier should be used for the cell at this // index path. NSString *reuseIdentifier = ...; // Use a dictionary of offscreen cells to get a cell for the reuse // identifier, creating a cell and storing it in the dictionary if one // hasn't already been added for the reuse identifier. WARNING: Don't // call the table view's dequeueReusableCellWithIdentifier: method here // because this will result in a memory leak as the cell is created but // never returned from the tableView:cellForRowAtIndexPath: method! UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier]; if (!cell) { cell = [[YourTableViewCellClass alloc] init]; [self.offscreenCells setObject:cell forKey:reuseIdentifier]; } // Configure the cell with content for the given indexPath, for example: // cell.textLabel.text = someTextForThisCell; // ... // Make sure the constraints have been set up for this cell, since it // may have just been created from scratch. Use the following lines, // assuming you are setting up constraints from within the cell's // updateConstraints method: [cell setNeedsUpdateConstraints]; [cell updateConstraintsIfNeeded]; // Set the width of the cell to match the width of the table view. This // is important so that we'll get the correct cell height for different // table view widths if the cell's height depends on its width (due to // multi-line UILabels word wrapping, etc). We don't need to do this // above in -[tableView:cellForRowAtIndexPath] because it happens // automatically when the cell is used in the table view. Also note, // the final width of the cell may not be the width of the table view in // some cases, for example when a section index is displayed along // the right side of the table view. You must account for the reduced // cell width. cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds)); // Do the layout pass on the cell, which will calculate the frames for // all the views based on the constraints. (Note that you must set the // preferredMaxLayoutWidth on multi-line UILabels inside the // -[layoutSubviews] method of the UITableViewCell subclass, or do it // manually at this point before the below 2 lines!) [cell setNeedsLayout]; [cell layoutIfNeeded]; // Get the actual height required for the cell's contentView CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; // Add an extra point to the height to account for the cell separator, // which is added between the bottom of the cell's contentView and the // bottom of the table view cell. height += 1.0; return height;}// NOTE: Set the table view's estimatedRowHeight property instead of // implementing the below method, UNLESS you have extreme variability in // your row heights and you notice the scroll indicator "jumping" // as you scroll.- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{ // Do the minimal calculations required to be able to return an // estimated row height that's within an order of magnitude of the // actual height. For example: if ([self isTallCellAtIndexPath:indexPath]) { return 350.0; } else { return 40.0; }}
示例項(xiàng)目
iOS 8示例項(xiàng)目 - 需要iOS 8
iOS 7示例項(xiàng)目 - 適用于iOS 7+
這些項(xiàng)目是具有可變行高的表視圖的完整工作示例,因?yàn)楸硪晥D單元格包含UILabels中的動(dòng)態(tài)內(nèi)容。
Xamarin(C#/。NET)
如果您使用Xamarin,看看這個(gè)樣本項(xiàng)目由放在一起@KentBoogaart。

TA貢獻(xiàn)1995條經(jīng)驗(yàn) 獲得超2個(gè)贊
對(duì)于IOS8來說,它非常簡(jiǎn)單:
override func viewDidLoad() { super.viewDidLoad() self.tableView.estimatedRowHeight = 80 self.tableView.rowHeight = UITableViewAutomaticDimension}
要么
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension}
但對(duì)于IOS7,關(guān)鍵是自動(dòng)布局后計(jì)算高度,
func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat { cell.setNeedsLayout() cell.layoutIfNeeded() let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0 return height}
重要
如果有多行標(biāo)簽,請(qǐng)不要忘記設(shè)置
numberOfLines
為0
。別忘了
label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
完整的示例代碼在這里。
編輯 Swift 4.2 UITableViewAutomaticDimension
改為 UITableView.automaticDimension

TA貢獻(xiàn)1817條經(jīng)驗(yàn) 獲得超14個(gè)贊
我將@ smileyborg的iOS7解決方案包裝在一個(gè)類別中
我決定將@smileyborg的這個(gè)聰明的解決方案包裝成一個(gè)UICollectionViewCell+AutoLayoutDynamicHeightCalculation
類別。
該類別還糾正了@ wildmonkey的答案(從筆尖加載單元格并systemLayoutSizeFittingSize:
返回CGRectZero
)中列出的問題。
它沒有考慮任何緩存,但現(xiàn)在適合我的需求。隨意復(fù)制,粘貼和破解它。
UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h
#import <UIKit/UIKit.h>
typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);
/**
* A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
*
* Many thanks to @smileyborg and @wildmonkey
*
* @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
*/
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)
/**
* Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
*
* @param name Name of the nib file.
*
* @return collection view cell for using to calculate content based height
*/
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;
/**
* Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
*
* @param block Render the model data to your UI elements in this block
*
* @return Calculated constraint derived height
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;
/**
* Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;
@end
UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m
#import "UICollectionViewCell+AutoLayout.h"
@implementation UICollectionViewCell (AutoLayout)
#pragma mark Dummy Cell Generator
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
[heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
return heightCalculationCell;
}
#pragma mark Moving Constraints
- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
[self removeConstraint:constraint];
id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}];
}
#pragma mark Height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
NSParameterAssert(block);
block();
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return calculatedSize.height;
}
@end
用法示例:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
[(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
}];
return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}
值得慶幸的是,我們不必在iOS8中做這個(gè)爵士樂,但現(xiàn)在就是這樣!

TA貢獻(xiàn)1880條經(jīng)驗(yàn) 獲得超4個(gè)贊
這是我的解決方案。您需要在加載視圖之前告訴TableView estimatedHeight。否則它將無法像預(yù)期的那樣表現(xiàn)。
Objective-C的
- (void)viewWillAppear:(BOOL)animated { _messageField.delegate = self; _tableView.estimatedRowHeight = 65.0; _tableView.rowHeight = UITableViewAutomaticDimension;}
更新到Swift 4.2
override func viewWillAppear(_ animated: Bool) { tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 65.0}
- 4 回答
- 0 關(guān)注
- 991 瀏覽
添加回答
舉報(bào)