亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

在UITableView中使用自動布局來獲取動態單元格布局和可變行高

在UITableView中使用自動布局來獲取動態單元格布局和可變行高

iOS
梵蒂岡之花 2019-05-22 13:38:21
在UITableView中使用自動布局來獲取動態單元格布局和可變行高如何UITableViewCell在表格視圖中使用s 內的自動布局,讓每個單元格的內容和子視圖確定行高(本身/自動),同時保持平滑的滾動性能?
查看完整描述

4 回答

?
holdtom

TA貢獻1805條經驗 獲得超10個贊

TL; DR:不喜歡讀書嗎?直接跳轉到GitHub上的示例項目:

概念描述

無論您正在開發哪種iOS版本,以下前兩個步驟均適用。

1.設置和添加約束

UITableViewCell子類中,添加約束,以便單元格的子視圖的邊緣固定到單元格的contentView邊緣(最重要的是頂部和底部邊緣)。注意:不要將子視圖固定到單元格本身; 只到細胞的contentView!通過確保每個子視圖的垂直維度中的內容壓縮阻力內容擁抱約束不會被您添加的更高優先級約束覆蓋,讓這些子視圖的內在內容大小驅動表格視圖單元格內容視圖的高度。(嗯?點擊這里。

請記住,我們的想法是將單元格的子視圖垂直連接到單元格的內容視圖,以便它們可以“施加壓力”并使內容視圖擴展以適應它們。使用帶有幾個子視圖的示例單元格,這里是一個視覺圖示,說明您的約束的某些 (不是全部?。?/em>需要看起來像什么:

表視圖單元格上的約束的示例說明。

您可以想象,隨著更多文本被添加到上面示例單元格中的多行正文標簽,它將需要垂直增長以適合文本,這將有效地迫使單元格在高度上增長。(當然,您需要正確的約束才能使其正常工作?。?/p>

獲得正確的約束絕對是使用自動布局獲得動態單元格高度最困難和最重要的部分。如果你在這里犯了錯誤,它可能會阻止其他一切工作 - 所以慢慢來!我建議在代碼中設置約束,因為您確切地知道在哪里添加了哪些約束,并且在出現問題時更容易調試。在代碼中添加約束可能與使用布局錨點的Interface Builder或GitHub上可用的一個非常棒的開源API一樣簡單并且功能強大得多。

  • 如果要在代碼中添加約束,則應該在updateConstraintsUITableViewCell子類的方法中執行此操作一次。請注意,updateConstraints可能會多次調用,因此為了避免多次添加相同的約束,請確保updateConstraints在檢查布爾屬性(例如,didSetupConstraints在運行約束后設置為YES)中包含約束添加代碼 - 添加代碼一次)。另一方面,如果您有更新現有約束的代碼(例如constant在某些約束上調整屬性),請將其放在updateConstraints檢查中但不在檢查之外,didSetupConstraints以便每次調用方法時都可以運行它。

2.確定唯一的表格視圖單元格重用標識符

對于單元中每個唯一的約束集,請使用唯一的單元重用標識符。換句話說,如果您的單元格具有多個唯一布局,則每個唯一布局應接收其自己的重用標識符。(當您的單元格變體具有不同數量的子視圖,或者子視圖以不同的方式排列時,您需要使用新的重用標識符的良好提示。)

例如,如果您在每個單元格中顯示電子郵件,則可能有4種獨特的布局:僅包含主題的郵件,包含主題和正文的郵件,包含主題和照片附件的郵件以及包含主題的郵件,身體和照片附件。每個布局都有完全不同的約束來實現它,因此一旦初始化單元并為這些單元類型之一添加約束,單元應該獲得特定于該單元類型的唯一重用標識符。這意味著當您將單元格出列以便重復使用時,已經添加了約束并準備好使用該單元格類型。

請注意,由于內在內容大小的差異,具有相同約束(類型)的單元格可能仍然具有不同的高度!由于內容的大小不同,不要將根本不同的布局(不同的約束)與不同的計算視圖幀(由相同的約束條件解決)混淆。

  • 不要將具有完全不同約束集的單元添加到同一重用池(即使用相同的重用標識符),然后嘗試刪除舊約束并在每次出列后從頭開始設置新約束。內部自動布局引擎不是為處理約束中的大規模更改而設計的,您將看到大量的性能問題。

適用于iOS 8 - Self-Sizing Cells

3.啟用行高估計

要啟用自調整大小的表視圖單元格,必須將表視圖的rowHeight屬性設置為UITableViewAutomaticDimension。您還必須為estimatedRowHeight屬性分配值。一旦設置了這兩個屬性,系統就會使用“自動布局”來計算行的實際高度

Apple:使用自定義表格查看單元格

在iOS 8中,Apple已經內置了以前必須在iOS 8之前實現的大部分工作。為了使自定義單元機制能夠工作,必須首先將rowHeight表視圖上的屬性設置為常量UITableViewAutomaticDimension。然后,您只需通過將表視圖的estimatedRowHeight屬性設置為非零值來啟用行高估計,例如:

self.tableView.rowHeight = UITableViewAutomaticDimension;self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is

這樣做是為表視圖提供臨時估計/占位符,用于尚未在屏幕上顯示的單元格的行高。然后,當這些單元格即將在屏幕上滾動時,將計算實際行高。要確定每一行的實際高度,表視圖contentView會根據內容視圖的已知固定寬度(基于表視圖的寬度,減去任何其他內容,如節索引)自動詢問每個單元格需要的高度?;蚋郊晥D)以及已添加到單元格內容視圖和子視圖中的自動布局約束。確定此實際單元格高度后,將使用新的實際高度更新行的舊估計高度(并且根據需要對表視圖的contentSize / contentOffset進行任何調整)。

一般來說,您提供的估計值不必非常準確 - 它僅用于在表格視圖中正確調整滾動指示器的大小,并且表格視圖可以很好地調整滾動指示器以獲得不正確的估計值在屏幕上滾動單元格。您應該將estimatedRowHeight表視圖(在viewDidLoad或類似)中的屬性設置為常量值,即“平均”行高。只有當您的行高具有極端可變性(例如,相差一個數量級)并且您在滾動時注意到滾動指示符“跳躍”時,您仍然tableView:estimatedHeightForRowAtIndexPath:需要執行所需的最小計算以返回對每行更準確的估計。

對于iOS 7支持(自己實現自動細胞大小調整)

3.進行布局通過并獲取單元格高度

首先,實例化表視圖單元的屏幕外實例,每個重用標識符的一個實例,嚴格用于高度計算。(屏幕外意味著單元格引用存儲在視圖控制器上的屬性/ ivar中,并且永遠不會從tableView:cellForRowAtIndexPath:表格視圖返回以實際在屏幕上呈現。)接下來,必須使用確切內容(例如文本,圖像等)配置單元格如果要在表格視圖中顯示它將保持。

然后,迫使立即布局其子視圖的細胞,然后使用systemLayoutSizeFittingSize:該方法UITableViewCellcontentView找出電池的必要高度是什么。使用UILayoutFittingCompressedSize去適應單元格中的所有內容所需的最小尺寸。然后可以從tableView:heightForRowAtIndexPath:委托方法返回高度。

4.使用估計行高

如果你的表視圖中有超過幾十行,你會發現在第一次加載表視圖時,執行自動布局約束求解會很快使主線程陷入困境,就像在第一次加載時tableView:heightForRowAtIndexPath:調用每一行一樣(為了計算滾動指示器的大?。?。

從iOS 7開始,您可以(并且絕對應該)estimatedRowHeight在表視圖中使用該屬性。這樣做是為表視圖提供臨時估計/占位符,用于尚未在屏幕上顯示的單元格的行高。然后,當這些單元格即將在屏幕上滾動時,將計算實際行高(通過調用tableView:heightForRowAtIndexPath:),并使用實際行更新估計的高度。

一般來說,您提供的估計值不必非常準確 - 它僅用于在表格視圖中正確調整滾動指示器的大小,并且表格視圖可以很好地調整滾動指示器以獲得不正確的估計值在屏幕上滾動單元格。您應該將estimatedRowHeight表視圖(在viewDidLoad或類似)中的屬性設置為常量值,即“平均”行高。只有當您的行高具有極端可變性(例如,相差一個數量級)并且您在滾動時注意到滾動指示符“跳躍”時,您仍然tableView:estimatedHeightForRowAtIndexPath:需要執行所需的最小計算以返回對每行更準確的估計。

5.(如果需要)添加行高度緩存

如果你已經完成了上述所有工作,并且在進行約束求解時仍然發現性能慢得令人無法接受tableView:heightForRowAtIndexPath:,那么很遺憾,你需要為單元高度實現一些緩存。(這是Apple工程師建議的方法。)一般的想法是讓Auto Layout引擎第一次解決約束,然后緩存該單元格的計算高度,并將緩存值用于該單元格高度的所有未來請求。當然,技巧是確保在發生任何可能導致單元格高度發生變化的情況時清除單元格的緩存高度 - 主要是當單元格的內容發生變化或其他重要事件發生時(如用戶調整)動態類型文本大小滑塊)。

iOS 7通用示例代碼(有很多多汁的評論)

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

示例項目

這些項目是具有可變行高的表視圖的完整工作示例,因為表視圖單元格包含UILabels中的動態內容。

Xamarin(C#/。NET)

如果您使用Xamarin,看看這個樣本項目由放在一起@KentBoogaart。


查看完整回答
反對 回復 2019-05-22
?
拉風的咖菲貓

TA貢獻1995條經驗 獲得超2個贊

對于IOS8來說,它非常簡單:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension}

要么

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension}

但對于IOS7,關鍵是自動布局后計算高度,

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height}

重要

  • 如果有多行標簽,請不要忘記設置numberOfLines0

  • 別忘了 label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

完整的示例代碼在這里。

編輯 Swift 4.2 UITableViewAutomaticDimension改為 UITableView.automaticDimension


查看完整回答
反對 回復 2019-05-22
?
大話西游666

TA貢獻1817條經驗 獲得超14個贊

我將@ smileyborg的iOS7解決方案包裝在一個類別中

我決定將@smileyborg的這個聰明的解決方案包裝成一個UICollectionViewCell+AutoLayoutDynamicHeightCalculation類別。

該類別還糾正了@ wildmonkey的答案(從筆尖加載單元格并systemLayoutSizeFittingSize:返回CGRectZero)中列出的問題。

它沒有考慮任何緩存,但現在適合我的需求。隨意復制,粘貼和破解它。

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中做這個爵士樂,但現在就是這樣!


查看完整回答
反對 回復 2019-05-22
?
慕村225694

TA貢獻1880條經驗 獲得超4個贊

這是我的解決方案。您需要在加載視圖之前告訴TableView estimatedHeight。否則它將無法像預期的那樣表現。

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}


查看完整回答
反對 回復 2019-05-22
  • 4 回答
  • 0 關注
  • 992 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號