簡體   English   中英

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

[英]Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

如何在表格視圖中的UITableViewCell使用 Auto Layout 來讓每個單元格的內容和子視圖確定行高(本身/自動),同時保持平滑的滾動性能?

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

概念描述

無論您為哪個 iOS 版本開發,下面的前 2 個步驟都適用。

1. 設置和添加約束

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

請記住,這個想法是讓單元格的子視圖垂直連接到單元格的內容視圖,以便它們可以“施加壓力”並使內容視圖擴展以適應它們。 使用帶有幾個子視圖的示例單元格,以下是一些(不是全部!)約束條件的可視化說明:

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

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

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

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

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

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

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

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

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

對於 iOS 8 - 調整單元格大小

3. 啟用行高估計

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

Apple: 使用自適應表格視圖單元格

在 iOS 8 中,Apple 已經內化了之前必須由您在 iOS 8 之前實現的大部分工作。為了允許自調整單元格機制起作用,您必須首先將 table view 上的rowHeight屬性設置為常量UITableView.automaticDimension 然后,您只需要通過將表視圖的estimatedRowHeight屬性設置為非零值來啟用行高估計,例如:

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

這樣做是為表格視圖提供一個臨時估計/占位符,用於尚未出現在屏幕上的單元格的行高。 然后,當這些單元格即將在屏幕上滾動時,將計算實際行高。 為了確定每一行的實際高度,表格視圖會根據內容視圖的已知固定寬度(基於表格視圖的寬度,減去任何其他內容,如部分)自動詢問每個單元格的contentView需要什么高度索引或附件視圖)以及您添加到單元格內容視圖和子視圖的自動布局約束。 一旦確定了該實際單元格高度,該行的舊估計高度將更新為新的實際高度(並且根據您的需要對表格視圖的 contentSize/contentOffset 進行任何調整)。

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

對於 iOS 7 支持(自己實現自動單元格大小調整)

3. 做一個布局傳遞並獲取單元格高度

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

然后,強制單元格立即布局其子視圖,然后使用UITableViewCellcontentView上的systemLayoutSizeFittingSize:方法找出單元格所需的高度。 使用UILayoutFittingCompressedSize獲得適合單元格所有內容所需的最小尺寸。 然后可以從tableView:heightForRowAtIndexPath:委托方法返回高度。

4. 使用估計的行高

如果你的 table view 有幾十行,你會發現在第一次加載 table view 時,做 Auto Layout 約束求解會很快卡住主線程,因為tableView:heightForRowAtIndexPath:會在每一行上調用在第一次加載時(為了計算滾動指示器的大小)。

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

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

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

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

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 multiline 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 整理的這個示例項目

對於上面的 iOS 8,它真的很簡單:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableView.automaticDimension
}

或者

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

但是對於iOS 7,關鍵是計算autolayout后的高度:

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

重要的

  • 如果有多行標簽,不要忘記將numberOfLines設置為0

  • 不要忘記label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

完整的示例代碼在這里

可變高度 UITableViewCell 的 Swift 示例

為 Swift 3 更新

William Hu 的 Swift 回答很好,但它幫助我在第一次學習做某事時有一些簡單而詳細的步驟。 下面的示例是我在學習制作具有可變單元格高度的UITableView時的測試項目。 我基於Swift 的這個基本 UITableView 示例

完成的項目應該是這樣的:

在此處輸入圖片說明

創建一個新項目

它可以只是一個單一視圖應用程序。

添加代碼

將一個新的 Swift 文件添加到您的項目中。 將其命名為 MyCustomCell。 這個類將保存您添加到故事板中單元格的視圖的出口。 在這個基本示例中,我們將在每個單元格中只有一個標簽。

import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UILabel!
}

我們稍后會連接這個插座。

打開 ViewController.swift 並確保您有以下內容:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

重要的提示:

  • 以下兩行代碼(以及自動布局)使可變單元格高度成為可能:

     tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension

設置故事板

將一個 Table View 添加到您的視圖控制器並使用自動布局將其固定到四個邊。 然后將表視圖單元格拖到表視圖上。 並在原型單元格上拖動一個標簽。 使用自動布局將標簽固定到表格視圖單元格內容視圖的四個邊緣。

在此處輸入圖片說明

重要的提示:

  • 自動布局與我上面提到的兩行重要代碼一起工作。 如果您不使用自動布局,它將無法正常工作。

其他 IB 設置

自定義類名和標識符

選擇Table View Cell,設置自定義類為MyCustomCell (我們添加的Swift文件中的類名)。 cellReuseIdentifier標識符設置為cell (與我們在上面代碼中用於cellReuseIdentifier字符串相同。

在此處輸入圖片說明

標簽的零線

在標簽中將行數設置為0 這意味着多行並允許標簽根據其內容調整自身大小。

在此處輸入圖片說明

連接插座

  • 控制從 storyboard 中的 Table View 拖動到ViewController代碼中的tableView變量。
  • 對原型單元格中的myCellLabelMyCustomCell類中的myCellLabel變量執行相同的myCellLabel

完成的

您現在應該能夠運行您的項目並獲得具有可變高度的單元格。

筆記

  • 此示例僅適用於 iOS 8 及更高版本。 如果您仍然需要支持 iOS 7,那么這對您不起作用。
  • 在您未來的項目中,您自己的自定義單元格可能會有多個標簽。 確保您將所有內容固定正確,以便自動布局可以確定要使用的正確高度。 您可能還必須使用垂直壓縮阻力和擁抱。 有關更多信息,請參閱這篇文章
  • 如果您沒有固定前緣和后緣(左右),您可能還需要設置標簽的preferredMaxLayoutWidth以便它知道何時換行。 例如,如果您在上面的項目中為標簽添加了水平居中約束而不是固定前緣和后緣,那么您需要將此行添加到tableView:cellForRowAtIndexPath方法:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

也可以看看

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

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

該類別還糾正了@wildmonkey 的回答中概述的問題(從筆尖和systemLayoutSizeFittingSize:加載單元格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 中做這種爵士樂,但現在就可以了!

這是我的解決方案:

您需要在加載視圖之前告訴TableView estimatedHeight TableView 否則它將無法像預期的那樣運行。

目標-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
}

@smileyborg 提出的解決方案幾乎是完美的。 如果您有一個自定義單元格並且您想要一個或多個具有動態高度的UILabel ,那么systemLayoutSizeFittingSize方法與啟用AutoLayout相結合將返回一個CGSizeZero除非您將所有單元格約束從單元格移動到它的 contentView (如@TomSwift 在這里建議的如何調整大小)超級視圖以適合所有具有自動布局的子視圖? )。

為此,您需要在自定義 UITableViewCell 實現中插入以下代碼(感謝@Adrian)。

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

將@smileyborg 的答案與此混合應該有效。

一個足夠重要的問題,我剛剛遇到並作為答案發布。

@smileyborg 的回答大部分是正確的。 但是,如果您在自定義單元格類的layoutSubviews方法中有任何代碼,例如設置preferredMaxLayoutWidth ,則不會使用以下代碼運行:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

這讓我困惑了一段時間。 然后我意識到這是因為那些只觸發contentView上的 layoutSubviews ,而不是單元格本身。

我的工作代碼如下所示:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

請注意,如果您正在創建一個新單元格,我很確定您不需要調用setNeedsLayout因為它應該已經設置好了。 在保存對單元格的引用的情況下,您可能應該調用它。 無論哪種方式,它都不應該傷害任何東西。

另一個提示,如果您使用單元格子類來設置諸如preferredMaxLayoutWidth 正如@smileyborg 提到的,“您的表格視圖單元格的寬度尚未固定為表格視圖的寬度”。 這是真的,如果你在你的子類中而不是在視圖控制器中做你的工作,那就麻煩了。 但是,此時您可以使用表格寬度簡單地設置單元格框架:

例如在計算高度時:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(我碰巧緩存了這個特定的單元以供重用,但這無關緊要)。

萬一人們仍然有這個問題。 我寫了一篇關於將 Autolayout 與 UITableViews 結合使用的快速博客文章, 利用 Autolayout For Dynamic Cell Heights以及一個開源組件來幫助使其更抽象和更容易實現。 https://github.com/Raizlabs/RZCellSizeManager

(對於 Xcode 8.x / Xcode 9.x 在底部讀取)

請注意 Xcode 7.x 中的以下問題,這可能會引起混淆:

Interface Builder 無法正確處理自動調整大小的單元格設置。 即使你的約束是絕對有效的,IB 仍然會抱怨並給你混亂的建議和錯誤。 原因是 IB 不願意按照您的約束要求更改行的高度(以便單元格適合您的內容)。 相反,它保持行的高度固定並開始建議您更改您的約束,您應該忽略

例如,假設您已經設置好一切,沒有警告,沒有錯誤,一切正常。

在此處輸入圖片說明

現在,如果您更改字體大小(在本示例中,我將描述標簽字體大小從 17.0 更改為 18.0)。

在此處輸入圖片說明

因為字體變大了,標簽現在要占據 3 行(之前它是占據 2 行)。

如果 Interface Builder 按預期工作,它將調整單元格的高度以適應新的標簽高度。 然而,實際發生的是 IB 顯示紅色自動布局錯誤圖標並建議您修改擁抱/壓縮優先級。

在此處輸入圖片說明

您應該忽略這些警告。 您可以*做的是手動更改行的高度(選擇單元格>大小檢查器>行高)。

在此處輸入圖片說明

我一次單擊一次(使用向上/向下步進器)更改此高度,直到紅色箭頭錯誤消失! (您實際上會收到黃色警告,此時只需繼續執行“更新幀”,它應該都能正常工作)。

* 請注意,您實際上不必在 Interface Builder 中解決這些紅色錯誤或黃色警告 - 在運行時,一切都會正常工作(即使 IB 顯示錯誤/警告)。 只需確保在運行時在控制台日志中您沒有收到任何 AutoLayout 錯誤。

事實上,試圖總是在 IB 中更新行高是非常煩人的,有時幾乎不可能(因為小數值)。

為了防止煩人的 IB 警告/錯誤,您可以選擇所涉及的視圖並在Size Inspector為屬性Ambiguity選擇Verify Position Only

在此處輸入圖片說明


Xcode 8.x / Xcode 9.x 似乎(有時)做的事情與 Xcode 7.x 不同,但仍然不正確。 例如,即使compression resistance priority / hugging priority設置為 required (1000),Interface Builder 也可能會拉伸或剪裁標簽以適合單元格(而不是調整單元格高度以適合標簽周圍)。 在這種情況下,它甚至可能不會顯示任何 AutoLayout 警告或錯誤。 或者有時它完全符合 Xcode 7.x 所做的,如上所述。

只要您在單元格中的布局良好。

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

更新:您應該使用 iOS 8 中引入的動態調整大小。

要為行高和估計行高設置自動尺寸,請確保按照以下步驟使自動尺寸對單元格/行高布局有效。

  • 分配和實現 tableview 數據源和委托
  • UITableViewAutomaticDimension分配給 rowHeight &estimatedRowHeight
  • 實現委托/數據源方法(即heightForRowAt並返回一個值UITableViewAutomaticDimension給它)

——

目標 C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return UITableViewAutomaticDimension;
}

迅速:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

對於 UITableviewCell 中的標簽實例

  • 設置行數 = 0(& 換行模式 = 截尾)
  • 設置與其父視圖/單元格容器相關的所有約束(頂部、底部、左側)。
  • 可選:設置標簽的最小高度,如果你想要標簽覆蓋的最小垂直區域,即使沒有數據。

在此處輸入圖片說明

注意:如果您有多個具有動態長度的標簽(UIElements),應根據其內容大小進行調整:為您希望以更高優先級展開/壓縮的標簽調整“內容擁抱和壓縮阻力優先級”。

就像@Bob-Spryn 一樣,我遇到了一個足夠重要的問題,因此我將其發布為答案。

我在@smileyborg 的回答中掙扎了一段時間。 我遇到的問題是,如果您在 IB 中使用其他元素( UILabelsUIButtons等)在 IB 中定義了原型單元格,當您使用 [ [YourTableViewCellClass alloc] init]實例化單元格時,它不會實例化所有其他[YourTableViewCellClass alloc] init]除非您編寫了代碼來執行此操作,否則該單元格中的元素。 (我對initWithStyle有類似的經歷。)

要讓故事板實例化所有附加元素,請使用[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (不是[tableView dequeueReusableCellWithIdentifier:forIndexPath:]因為這會導致有趣的問題。) IB 將被實例化。

動態表格視圖單元格高度和自動布局

一個解決storyboard Auto Layout問題的好方法:

- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
  static RWImageCell *sizingCell = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
  });

  [sizingCell setNeedsLayout];
  [sizingCell layoutIfNeeded];

  CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
  return size.height;
}

另一個“解決方案”:跳過所有這些挫折並使用 UIScrollView 來獲得與 UITableView 外觀和感覺相同的結果。

這對我來說是一個痛苦的“解決方案”,在我總共投入了 20 多個非常令人沮喪的小時試圖構建類似 Smileyborg 建議的東西並且失敗了幾個月和三個版本的 App Store 發布之后。

我的看法是,如果你真的需要 iOS 7 支持(對我們來說,這是必不可少的),那么這項技術太脆弱了,你會費力去嘗試。 而且 UITableView 通常是完全矯枉過正的,除非您使用一些高級行編輯功能和/或確實需要支持 1000 多個“行”(在我們的應用程序中,它實際上永遠不會超過 20 行)。

額外的好處是,與 UITableView 附帶的所有委托廢話和來回相比,代碼變得非常簡單。 它只是 viewOnLoad 中的一個單循環代碼,看起來優雅且易於管理。

以下是有關如何執行此操作的一些提示:

  1. 使用 Storyboard 或 nib 文件,創建一個 ViewController 和關聯的根視圖。

  2. 將 UIScrollView 拖到您的根視圖上。

  3. 將約束頂部、底部、左側和右側約束添加到頂級視圖,以便 UIScrollView 填充整個根視圖。

  4. 在 UIScrollView 中添加一個 UIView 並將其稱為“容器”。 向 UIScrollView(其父級)添加頂部、底部、左側和右側約束。 關鍵技巧:還要添加一個“等寬”約束來鏈接 UIScrollView 和 UIView。

    注意:您將收到錯誤消息“滾動視圖具有不明確的可滾動內容高度”,並且您的容器 UIView 的高度應為 0 像素。 當應用程序運行時,這兩個錯誤似乎都不重要。

  5. 為每個“單元”創建 nib 文件和控制器。 使用 UIView 而不是 UITableViewCell。

  6. 在您的根 ViewController 中,您基本上將所有“行”添加到容器 UIView 並以編程方式添加約束,將它們的左右邊緣鏈接到容器視圖,將它們的頂部邊緣鏈接到容器視圖頂部(對於第一項)或前一個細胞。 然后將最后一個單元格鏈接到容器底部。

對我們來說,每一“行”都在一個 nib 文件中。 所以代碼看起來像這樣:

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {
        
        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

這是 UITools.addViewToTop 的代碼:

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)
        
        //Set left and right constraints so fills full horz width:
        
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))
        
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))
        
        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

到目前為止,我發現這種方法唯一的“問題”是 UITableView 有一個很好的功能,即滾動時視圖頂部的“浮動”部分標題。 除非您添加更多編程,否則上述解決方案不會這樣做,但對於我們的特殊情況,此功能不是 100% 必不可少的,並且當它消失時沒有人注意到。

如果您想在單元格之間設置分隔線,只需在自定義“單元格”底部添加一個 1 像素高的 UIView,看起來像分隔線。

一定要打開“反彈”和“垂直反彈”以使刷新控件工作,因此它看起來更像是一個表格視圖。

TableView 會在您的內容下顯示一些空行和分隔符,如果它沒有填滿全屏,而這個解決方案沒有。 但就我個人而言,我更喜歡那些空行不存在的情況 - 可變單元格高度在我看來總是“有問題”,在那里有空行。

希望其他程序員在浪費 20 多個小時試圖在他們自己的應用程序中使用 Table View 解決問題之前閱讀我的帖子。 :)

tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

在此處輸入圖片說明

我不得不使用動態視圖(通過代碼設置視圖和約束),當我想設置 preferredMaxLayoutWidth 標簽的寬度為 0 時。所以我得到了錯誤的單元格高度。

然后我加了

[cell layoutSubviews];

執行前

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

在該標簽的寬度符合預期並且動態高度計算正確之后。

假設您有一個帶有子視圖的單元格,並且您希望單元格的高度足夠高以包含子視圖 + 填充。

1) 設置子視圖的底部約束等於 cell.contentView 減去你想要的填充。 不要對單元格或 cell.contentView 本身設置約束。

2) 將 tableView 的rowHeight屬性或tableView:heightForRowAtIndexPath:UITableViewAutomaticDimension

3)無論是設置的tableView的estimatedRowHeight財產或tableView:estimatedHeightForRowAtIndexPath:以高度的最佳猜測。

就是這樣。

如果您以編程方式進行布局,以下是在 Swift 中使用錨點的 iOS 10 需要考慮的事項。

有三個規則/步驟

NUMBER 1:在 viewDidLoad 上設置 tableview 的這兩個屬性,第一個是告訴 tableview 應該期望其單元格上的動態大小,第二個只是讓應用程序計算滾動條指示器的大小,因此它有助於表現。

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

數字 2:這很重要,您需要將子視圖添加到單元格的 contentView 而不是視圖,並且還使用它的 layoutsmarginguide 將子視圖錨定到頂部和底部,這是如何做到這一點的工作示例。

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

創建一個方法來添加子視圖並執行布局,在 init 方法中調用它。

數字 3:不要調用方法:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

如果你這樣做,你將覆蓋你的實現。

在 tableviews 中遵循這 3 條動態單元格規則。

這是一個有效的實現https://github.com/jamesrochabrun/MinimalViewController

如果你有一個很長的字符串。 例如一個沒有換行符的。 那么你可能會遇到一些問題。

接受的答案和其他幾個答案都提到了“所謂的”修復。 你只需要添加

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

我發現蘇拉格的回答最完整和簡潔,因此不會令人困惑。

雖然沒有解釋為什么需要這些改變。 讓我們這樣做。

將以下代碼放入項目中。

import UIKit

class ViewController: UIViewController {

    lazy var label : UILabel = {
        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.backgroundColor = .red
        lbl.textColor = .black
        return lbl
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // step0: (0.0, 0.0)
        print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step1: (29.0, 20.5)
        label.text = "hiiiii"
        print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step2: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints"
        print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step3: (992.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
        print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step4: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
        print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step5: (328.0, 61.0)
        label.numberOfLines = 0
        print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step6: (98.5, 243.5)
        label.preferredMaxLayoutWidth = 100
        print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")

        setupLayout()
    }
    func setupLayout(){
        view.addSubview(label)
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
}

請注意,我沒有添加任何大小限制。 我只添加了 centerX、centerY 約束。 但是標簽的尺寸仍然正確,為什么?

因為contentSize

為了更好地處理這個問題,首先保留 step0,然后注釋掉步驟 1-6。 setupLayout()留下來。 觀察行為。

然后取消注釋step1,並觀察。

然后取消注釋 step2 並觀察。

這樣做直到您取消了所有 6 個步驟的注釋並觀察了它們的行為。

從這一切可以得出什么結論? 哪些因素可以改變contenSize

  1. 文本長度:如果你有更長的文本,那么你的內在內容大小的寬度會增加
  2. 換行符:如果添加\\n則內在內容大小的寬度將是所有行的最大寬度。 如果一行有 25 個字符,另一行有 2 個字符,另一行有 21 個字符,那么您的寬度將根據 25 個字符計算
  3. 允許的行數:您必須將numberOfLines設置為0否則您將不會有多行。 您的numberOfLines將調整您的內在內容大小的高度
  4. 進行調整:想象一下,根據您的文本,您的內在內容大小的寬度為200 ,高度為100 ,但您想將寬度限制為標簽的容器,您打算怎么做? 解決方案是將其設置為所需的寬度。 您可以通過將preferredMaxLayoutWidth設置為130來做到這一點,然后您的新內在內容大小將具有大約130的寬度。 高度顯然會超過100因為您需要更多的線條。 話雖如此,如果您的約束設置正確,那么您根本不需要使用它! 有關更多信息,請參閱此答案及其評論。 如果您沒有限制寬度/高度的約束,您只需要使用preferredMaxLayoutWidth ,因為有人可能會說“除非文本超過preferredMaxLayoutWidth否則不要換行”。 但是,如果您將前導/尾隨和numberOfLines0則可以 100% 確定,那么您就很好! 長話短說,這里推薦使用它的大多數答案都是錯誤的! 你不需要它。 需要它表明您的約束設置不正確或您只是沒有約束

  5. 字體大小:另請注意,如果您增加 fontSize,那么 internalContentSize 的高度將增加。 我沒有在我的代碼中顯示這一點。 你可以自己試試。

所以回到你的 tableViewCell 示例:

您需要做的就是:

  • numberOfLines設置為0
  • 將標簽正確約束到邊距/邊緣
  • 無需設置preferredMaxLayoutWidth

在我的情況下,我必須創建一個帶有來自服務器的圖像的自定義單元格,並且可以是任何寬度和高度。 和兩個具有動態大小(寬度和高度)的 UILabels

我在我的回答中通過自動布局和以編程方式實現了同樣的目標:

基本上高於@smileyBorg 的回答有幫助,但 systemLayoutSizeFittingSize 從來沒有為我工作過,在我的方法中:

1. 沒有使用自動行高計算屬性。 2. 不使用估計高度 3. 不需要不必要的更新約束。 4.不使用自動首選最大布局寬度。 5. 不使用systemLayoutSizeFittingSize (應該有用但對我不起作用,我不知道它在內部做什么),而是我的方法 -(float)getViewHeight 工作,我知道它在內部做什么。

當我使用幾種不同的單元格顯示方式時,是否可以在 UITableView 單元格中具有不同的高度?

在我的情況下,填充是因為 sectionHeader 和 sectionFooter 高度,故事板允許我將其更改為最小 1。所以在 viewDidLoad 方法中:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

我只是做了一些愚蠢的嘗試和錯誤使用的2個值rowHeightestimatedRowHeight ,只是認為它可能提供一些見解調試:

如果您同時設置它們或僅設置estimatedRowHeight您將獲得所需的行為:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

建議您盡最大努力獲得正確的估計,但最終結果並沒有什么不同。 它只會影響你的表現。

在此處輸入圖片說明


如果你只設置 rowHeight 即只做:

tableView.rowHeight = UITableViewAutomaticDimension

你的最終結果不會如你所願:

在此處輸入圖片說明


如果設置estimatedRowHeight為1或更小,那么你會不管的崩潰rowHeight

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

我因以下錯誤消息而崩潰:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type
NSException

關於@smileyborg 接受的答案,我發現

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

在某些約束不明確的情況下不可靠。 通過使用下面 UIView 上的輔助類別,最好強制布局引擎計算一個方向的高度:

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

其中 w: 是 tableview 的寬度

只需在您的視圖控制器中添加這兩個函數即可解決您的問題。 在這里, list 是一個字符串數組,其中包含您的每一行的字符串。

 func tableView(_ tableView: UITableView, 
   estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])

    return (tableView.rowHeight) 
}

func calculateHeight(inString:String) -> CGFloat
{
    let messageString = input.text
    let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]

    let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)

    let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)

    let requredSize:CGRect = rect
    return requredSize.height
}
swift 4

    @IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var tableView: UITableView!
    private var context = 1
 override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
    }
  // Added observer to adjust tableview height based on the content

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &self.context{
            if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
                print("-----")
                print(size.height)
                tableViewHeightConstraint.constant = size.height + 50
            }
        }
    }

//Remove observer
 deinit {

        NotificationCenter.default.removeObserver(self)

    }

如果單元格高度是由內容動態變化的,您應該精確地將其計算出來,然后在呈現單元格之前返回高度值。 一個簡單的方法是在表格視圖單元格代碼中定義計數方法,以便控制器在表格單元格高度委托方法時調用。 如果高度依賴於表格或屏幕的寬度,請不要忘記計算實際單元格框架寬度(默認為 320)。 在表格單元格高度委托方法中,先使用cell.frame對單元格寬度進行修正,然后調用單元格中定義的計數高度方法獲取合適的值並返回

附注。 生成單元格對象的代碼可以定義在另一種方法中,供不同的表視圖單元格委托方法調用。

UITableView.automaticDimension可以通過 Interface Builder 設置:

Xcode > 故事板 > 尺寸檢查器

表格視圖單元格 > 行高 >自動

尺寸檢查員

Swift 中的另一個 iOs7+iOs8 解決方案

var cell2height:CGFloat=44

override func viewDidLoad() {
    super.viewDidLoad()
    theTable.rowHeight = UITableViewAutomaticDimension
    theTable.estimatedRowHeight = 44.0;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
    cell2height=cell.contentView.height
    return cell
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM