動的セルレイアウトと可変行の高さのためのUITableViewの自動レイアウトの使用


1501

UITableViewCellテーブルビューのs 内で自動レイアウトを使用して、スムーズなスクロールパフォーマンスを維持しながら、各セルのコンテンツとサブビューが行の高さを(それ自体/自動的に)決定できるようにする方法を教えてください。


swift 2.3のサンプルコードはこちらですgithub.com/dpakthakur/DynamicCellHeight
Deepak Thakur

複数行のUILabelを使用することに関するほとんどの回答を理解するには、方法contentSizepreferredMaxLayoutWidth機能を完全に理解する必要があります。こちらをご覧ください。とは言っても、制約を正しく設定すれば必要preferredMaxLayoutWidthありませんし、実際には予期しない結果が生じる可能性があります。
ハニー

回答:


2388

TL; DR:読書は嫌いですか?GitHubのサンプルプロジェクトに直接ジャンプします。

概念的な説明

以下の最初の2つのステップは、開発しているiOSのバージョンに関係なく適用されます。

1.制約の設定と追加

あなたにはUITableViewCell、セルのサブビューは、セルのの端に固定それらの縁があるように、サブクラス、制約を追加contentView(最も重要なの上端と下端にします)。注:サブビューをセル自体に固定しないでください。セルのcontentViewこれらのサブビューの固有のコンテンツサイズによって、各サブビューの垂直方向のコンテンツの圧縮抵抗コンテンツハグの制約が、追加した優先度の高い制約によって上書きされないようにすることで、テーブルビューセルのコンテンツビューの高さを高めます。(え?ここをクリック。

アイデアは、セルのサブビューをセルのコンテンツビューに垂直に接続して、「圧力をかけ」、コンテンツビューをそれらに合わせて拡大できるようにすることです。いくつかのサブビューを持つサンプルセルを使用して、制約の一部 (すべてではない!)がどのように見える必要があるかを視覚的に示します。

テーブルビューセルの制約の例の図。

上記の例のセルの複数行の本文ラベルにさらにテキストが追加されると、テキストに合わせて垂直方向に拡大する必要があり、セルの高さを効果的に拡大することが想像できます。(もちろん、これが正しく機能するためには、制約を正しく設定する必要があります!)

制約を正しく設定することは、自動レイアウトで動的なセルの高さを取得する上で、最も困難で最も重要な部分です。ここで間違えると、他のすべてが機能しなくなる可能性があります。時間をかけてください。コードに制約を設定することをお勧めします。どこに制約が追加されているかを正確に把握しており、問題が発生した場合のデバッグがはるかに簡単だからです。コードに制約を追加することは、レイアウトアンカーを使用したInterface BuilderやGitHubで利用できる素晴らしいオープンソースAPIの1つと同じくらい簡単で、はるかに強力です。

  • コードで制約を追加する場合updateConstraintsは、UITableViewCellサブクラスのメソッド内から一度行う必要があります。updateConstraints複数回呼び出される可能性があることに注意してください。同じ制約が複数回追加されるのを避けるために、制約を追加するコードupdateConstraintsdidSetupConstraints(制約を実行した後にYESに設定する)などのブールプロパティのチェック内にラップしてください。-コードを1回追加します)。一方、既存の制約を更新するコード(constant一部の制約のプロパティの調整など)updateConstraintsがある場合は、didSetupConstraintsこれをチェックの外に配置して、メソッドが呼び出されるたびに実行できるようにします。

2.一意のテーブルビューセル再利用識別子を決定する

セル内の一意の制約セットごとに、一意のセル再利用識別子を使用します。つまり、セルに複数の一意のレイアウトがある場合、各一意のレイアウトは独自の再利用識別子を受け取る必要があります。(新しい再利用識別子を使用する必要があることの良いヒントは、セルバリアントのサブビューの数が異なるか、サブビューが異なる方法で配置されている場合です。)

たとえば、各セルに電子メールメッセージを表示する場合、4つの固有のレイアウトがある可能性があります。件名のみのメッセージ、件名と本文のメッセージ、件名と写真の添付ファイルのあるメッセージ、件名のあるメッセージ、ボディ、写真添付。各レイアウトにはそれを実現するために必要な完全に異なる制約があるため、セルが初期化され、これらのセルタイプのいずれかに制約が追加されると、セルはそのセルタイプに固有の一意の再利用識別子を取得します。これは、再利用するためにセルをデキューするとき、制約はすでに追加されており、そのセルタイプに移行する準備ができていることを意味します。

固有のコンテンツサイズの違いにより、同じ制約(タイプ)を持つセルでも高さが異なる場合があることに注意してください。コンテンツのサイズが異なるため、根本的に異なるレイアウト(異なる制約)と異なる計算ビューフレーム(同一の制約から解決)を混同しないでください。

  • 完全に異なる制約のセットを持つセルを同じ再利用プールに追加しないでください(つまり、同じ再利用識別子を使用)。その後、古い制約を削除し、各デキュー後に新しい制約を最初から設定しようとします。内部の自動レイアウトエンジンは、制約の大規模な変更を処理するように設計されていないため、パフォーマンスの大きな問題が発生します。

iOS 8の場合-セルのサイズ変更

3.行の高さの推定を有効にする

自動サイズテーブルテーブルセルを有効にするには、テーブルビューのrowHeightプロパティをUITableViewAutomaticDimensionに設定する必要があります。また、estimatedRowHeightプロパティに値を割り当てる必要があります。これらのプロパティの両方が設定されるとすぐに、システムは自動レイアウトを使用して行の実際の高さを計算します

アップル:セルフサイジングテーブルビューセルの操作

iOS 8では、iOS 8より前にユーザーが実装する必要があった作業の多くがAppleによって内部化されています。セルサイズ調整セルメカニズムを機能させるrowHeightには、まずテーブルビューのプロパティを定数に設定する必要がありますUITableViewAutomaticDimension。次に、テーブルビューのestimatedRowHeightプロパティをゼロ以外の値に設定して、行の高さの推定を有効にするだけです。次に例を示します。

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

これは、まだ画面に表示されていないセルの行の高さの一時的な見積もり/プレースホルダーをテーブルビューに提供することです。次に、これらのセルが画面上でスクロールしようとすると、実際の行の高さが計算されます。各行の実際の高さを決定するために、テーブルビューcontentViewは、コンテンツビューの既知の固定幅(テーブルビューの幅からセクションインデックスなどの追加の要素を差し引いたもの)に基づいて必要な高さを各セルに自動的に尋ねますまたはアクセサリビュー)、およびセルのコンテンツビューとサブビューに追加した自動レイアウト制約。この実際のセルの高さが決定されると、行の古い推定高さが新しい実際の高さで更新されます(必要に応じて、テーブルビューのcontentSize / contentOffsetに対する調整が行われます)。

一般的に言えば、提供する見積もりはそれほど正確である必要はありません。これは、テーブルビューのスクロールインジケーターのサイズを正しく設定するためにのみ使用され、テーブルビューは、誤った見積もりに対してスクロールインジケーターを適切に調整します。画面上のセルをスクロールします。estimatedRowHeightテーブルビュー(viewDidLoadまたは同様のもの)のプロパティを、「平均」の行の高さである定数値に設定する必要があります。行の高さが非常に変動しやすく(たとえば、1桁異なる)、スクロール中にスクロールインジケーターが「ジャンプ」していることに気づいた場合にのみ、各行tableView:estimatedHeightForRowAtIndexPath:のより正確な推定値を返すために必要な最小限の計算を実行する必要があります。

iOS 7のサポート(自動セルサイズ設定を自分で実装)

3.レイアウトパスを実行してセルの高さを取得する

最初に、高さの計算にのみ使用される、再利用識別子ごと1つのインスタンスである、テーブルビューセルのオフスクリーンインスタンスをインスタンス化します。(オフスクリーンとは、セル参照がビューコントローラーのプロパティ/ ivarに保存されtableView:cellForRowAtIndexPath:、テーブルビューが実際に画面上にレンダリングするために返されないことを意味します。)次に、セルは正確なコンテンツ(テキスト、画像など)で構成する必要があります。テーブルビューに表示された場合に保持されます。

すぐにサブビューをレイアウトに続いて、セルを強制した後、使用systemLayoutSizeFittingSize:上のメソッドをUITableViewCellcontentViewセルの必要な高さが何であるかを見つけるために。UILayoutFittingCompressedSizeセルのすべての内容を収めるために必要な最小サイズを取得するために使用します。その後、高さはtableView:heightForRowAtIndexPath:デリゲートメソッドから返すことができます。

4.推定された行の高さを使用

テーブルビューに数十行以上ある場合、自動レイアウト制約の解決を実行するtableView:heightForRowAtIndexPath:と、最初のロード時に各行で呼び出されるように、テーブルビューを最初にロードするときにメインスレッドがすぐに停止する可能性があることがわかります(スクロールインジケーターのサイズを計算するため)。

iOS 7以降estimatedRowHeightでは、テーブルビューのプロパティを使用できます(絶対に使用する必要があります)。これは、まだ画面に表示されていないセルの行の高さの一時的な見積もり/プレースホルダーをテーブルビューに提供することです。次に、これらのセルが画面上でスクロールしようとすると、実際の行の高さが計算され(を呼び出すことによりtableView:heightForRowAtIndexPath:)、推定された高さが実際の高さで更新されます。

一般的に言えば、提供する見積もりはそれほど正確である必要はありません。これは、テーブルビューのスクロールインジケーターのサイズを正しく設定するためにのみ使用され、テーブルビューは、誤った見積もりに対してスクロールインジケーターを適切に調整します。画面上のセルをスクロールします。estimatedRowHeightテーブルビュー(viewDidLoadまたは同様のもの)のプロパティを、「平均」の行の高さである定数値に設定する必要があります。行の高さが非常に変動しやすく(たとえば、1桁異なる)、スクロール中にスクロールインジケーターが「ジャンプ」していることに気づいた場合にのみ、各行tableView:estimatedHeightForRowAtIndexPath:のより正確な推定値を返すために必要な最小限の計算を実行する必要があります。

5.(必要な場合)行の高さのキャッシュを追加する

上記のすべてを実行しても、で制約の解決を行っているときにパフォーマンスが許容できないほど遅いことにまだ気付いているtableView:heightForRowAtIndexPath:場合は、セルの高さのキャッシュを実装する必要があります。(これはAppleのエンジニアによって提案されたアプローチです。)一般的な考え方は、Autolayoutエンジンに初めて制約を解決させ、次にそのセルの計算された高さをキャッシュし、そのセルの高さに対する今後のすべてのリクエストにキャッシュされた値を使用することです。もちろん、トリックは、セルの高さを変更する可能性のある何かが発生したときに、セルのキャッシュされた高さを確実にクリアすることです。これは、主に、セルのコンテンツが変更されたとき、または他の重要なイベントが発生したとき(ユーザーが調整するような)ダイナミックタイプのテキストサイズスライダー)。

iOS 7 Generic Sample Code(たくさんのジューシーなコメント付き)

- (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がまとめたこのサンプルプロジェクトを確認してください。


1
これはうまく機能していますが、必要なサイズをわずかに見積もります(丸めのさまざまな問題が原因である可能性があります)。すべてのテキストをラベル内に収めるために、最終的な高さに2点を追加する必要があります
DBD

5
@ Alex311非常に興味深い。この例を提供してくれてありがとう。私は私の終わりにはほとんどのテストを行なったし、ここにいくつかのコメントまで書いた:github.com/Alex311/TableCellWithAutoLayout/commit/...
smileyborg

3
セルの再利用識別子タイプごとにセルをキャッシュすることを強くお勧めします。高さをデキューするたびに、追加されていないセルをキューから取り出します。キャッシュを使用すると、テーブルセルの初期化子が呼び出される回数を大幅に減らすことができます。なんらかの理由で、キャッシュしていないときに、スクロールすると、使用されているメモリの量が増え続けました。
Alex311 2013

1
ストーリーボード。これはプロトタイプセルでは機能しないと思います(少なくともVC全体を再インスタンス化しない限り)。でデキューしheightForRowAtIndexPath、セルを保持して次にcellForRowAtIndexPath呼び出されたときにそれを返すことで、完全にリークを回避できる可能性があります。
nschum 2014年

2
されたiOS8実装はまだのために推奨されるiOS9iOS10、またはこの回答が掲載されましたので、そこに新しいアプローチをしていますか?
公園

175

上記の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の場合、重要なのは自動レイアウト後の高さを計算することです。

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)

完全なサンプルコードはこちらです。


7
私たちはOS8ケースでheightForRowAtIndexPath機能を実装する必要はありませんだと思う
jpulikkottil

@eddwinpazが別の回答について言及しているように、行の高さをロックするために使用される制約にマージンが含まれていないことが重要です。
Richard

1
「複数行のラベルの場合は、numberOfLinesを0に設定することを忘れないでください」これにより、セルのサイズが動的ではなくなりました。
Ian-Fogelman、2017年

私をコントロールフリークと呼んでいますが、iOS 11の時代でさえ、UITableViewAutomaticDimensionの使用はまだ好きではありません。過去にいくつかの悪い経験をしました。そのため、私は通常、ここにリストされているiOS7ソリューションを使用しています。ここでlabel.preferredMaxLayoutWidthを忘れないようにするウィリアムのメモは私を救いました。
Brian Sachetta

スクロールすると、ラベルが複数行で表示され始め、行の高さも増加し
ません

94

可変高さUITableViewCellの迅速な例

Swift 3用に更新

ウィリアム・フーのスウィフトの回答は良いですが、何かを初めて学ぶときに、簡単で詳細な手順を実行するのに役立ちます。以下の例は、UITableView可変セルの高さでを作成することを学ぶ間の私のテストプロジェクトです。Swiftのこの基本的なUITableViewの例に基づいています

完成したプロジェクトは次のようになります。

ここに画像の説明を入力してください

新しいプロジェクトを作成する

単一のビューアプリケーションにすることもできます。

コードを追加する

プロジェクトに新しいSwiftファイルを追加します。MyCustomCellという名前を付けます。このクラスは、ストーリーボードのセルに追加するビューのアウトレットを保持します。この基本的な例では、各セルにラベルが1つだけあります。

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).")
    }
}

重要な注意点:

  • 可変セルの高さを可能にするのは、次の2行のコード(および自動レイアウト)です。

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

ストーリーボードを設定する

テーブルコントローラーをビューコントローラーに追加し、自動レイアウトを使用して4つの側面に固定します。次に、テーブルビューのセルをテーブルビューにドラッグします。そして、プロトタイプセルにラベルをドラッグします。自動レイアウトを使用して、ラベルをテーブルビューセルのコンテンツビューの4つの端に固定します。

ここに画像の説明を入力してください

重要な注意点:

  • 自動レイアウトは、上記の重要な2行のコードと連動します。自動レイアウトを使用しない場合は機能しません。

その他のIB設定

カスタムクラス名と識別子

テーブルビューセルを選択し、カスタムクラスをMyCustomCell(追加したSwiftファイル内のクラスの名前)に設定します。また、識別子をcellcellReuseIdentifier上記のコードで使用したのと同じ文字列)に設定します。

ここに画像の説明を入力してください

ラベルのゼロ線

0ラベルの行数を設定します。これは複数行を意味し、内容に基づいてラベルのサイズを変更できます。

ここに画像の説明を入力してください

アウトレットをフックアップ

  • ストーリーボードのテーブルビューからコードのtableView変数へのドラッグコントロールViewController
  • プロトタイプセルのラベルに対してmyCellLabelMyCustomCellクラスの変数に対して同じことを行います。

完成した

これでプロジェクトを実行して、高さが可変のセルを取得できるはずです。

ノート

  • この例は、iOS 8以降でのみ機能します。それでもiOS 7をサポートする必要がある場合、これは機能しません。
  • 将来のプロジェクトでの独自のカスタムセルには、おそらく複数のラベルがあります。自動レイアウトが使用する適切な高さを決定できるように、すべてが正しくピン留めされていることを確認してください。また、垂直方向の圧縮抵抗と抱擁を使用する必要がある場合があります。詳細については、この記事を参照してください。
  • リーディングエッジとトレーリングエッジ(左と右)を固定しない場合は、ラベルを設定して、preferredMaxLayoutWidth行を折り返すタイミングを認識させる必要があります。たとえば、前のエッジと後のエッジを固定するのではなく、上のプロジェクトのラベルにCenter Horizo​​ntally制約を追加した場合は、次の行をtableView:cellForRowAtIndexPathメソッドに追加する必要があります。

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

こちらもご覧ください


preferredMaxLayoutWidthセルに対してを設定する方が実際には良いのではない contentSizeですか?そのように、あなたがAccessoriesViewを持っているか、編集のためにスワイプされた場合、それはまだ考慮されますか?
ハニー(

@ハニー、あなたはとても正しいかもしれません。私は約1年間iOSについていませんでした、そして今すぐあなたに上手く答えることができません。
Suragch 2018年

65

@smileyborgのiOS7ソリューションをカテゴリにラップしました

私は、@ smileyborgによるこの賢い解決策をUICollectionViewCell+AutoLayoutDynamicHeightCalculationカテゴリーにまとめることに決めました。

このカテゴリは、@ wildmonkeyの回答で概説されている問題も修正します(nibからセルをロードして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でこのジャズを実行する必要はありませんが、今のところあります!


2
単にa:[YourCell new]を使用して、それをダミーとして使用できるようにする必要があります。制約コード作成コードがインスタンスで実行され、プログラムでレイアウトパスをトリガーする限り、問題ありません。
アダムウェイト2014

1
ありがとう!これは機能します。あなたのカテゴリーは素晴らしいです。この手法がうまく機能しUICollectionViewsていることを実感しました。
Ricardo Sanchez-Saez 2014

2
ストーリーボードで定義されたプロトタイプセルを使用してこれをどのように行いますか?
David Potter

57

これが私の解決策です:

あなたは指示する必要があり、それはビューをロードする前に。そうしないと、期待どおりに動作しません。TableViewestimatedHeight

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
}

2
自動レイアウトを正しく設定し、このコードをviewDidLoadに追加することでうまくいきました。
Bruce、

1
しかしestimatedRowHeight、行ごとに変化している場合はどうなりますか?見積もりを過大または過小にする必要がありますか?私が使用する高さの最小値または最大値を使用しtableViewますか?
ヤーノシュ

1
@JánosこれがrowHeightのポイントです。これを期待どおりに行うには、マージンのない制約を使用し、オブジェクトをTableViewCellに揃える必要があります。UITextViewを使用しているので、autoscroll = falseを削除する必要があります。そうしないと、高さが維持され、相対高さが期待どおりに機能しません。
Eddwin Paz

1
これは、最も堅牢なソリューションです。心配する必要はありません。estimatedRowHeightほとんどの場合、スクロールバーのサイズに影響し、実際のセルの高さには影響しません。選択する高さを太字にします。挿入/削除アニメーションに影響します。
SwiftArchitect 2016年

以下は、swift 2.3のサンプルコードですgithub.com/dpakthakur/DynamicCellHeight
Deepak Thakur

47

@smileyborgによって提案されたソリューションはほぼ完璧です。カスタムセルがありUILabel、動的な高さの1つ以上が必要な場合、AutoLayoutを有効にしたsystemLayoutSizeFittingSizeメソッドを組み合わせると、CGSizeZeroすべてのセル制約をセルからそのcontentViewに移動しない限り、が返されます(@TomSwiftで提案されているように、スーパービューのサイズを変更する方法すべてのサブビューをautolayout?でフィットしますか?)。

そのためには、カスタム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の回答とこれを混在させることで機能するはずです。


systemLayoutSizeFittingSizeセルではなくcontentViewで呼び出す必要がある
onmyway133

25

私がちょうど答えとして投稿するのに遭遇した重要な十分な落とし穴。

@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);

(私はたまたまこの特定のセルを再利用のためにキャッシュしていますが、それは無関係です)。



19

セル内のレイアウトが適切である限り。

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

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

更新:iOS 8で導入された動的サイズ変更を使用する必要があります。


これはiOS7で私のために働いていますtableView:cellForRowAtIndexPath:tableView:heightForRowAtIndexPath:今すぐ電話してもいいですか?
アリ

[OK]を、このように動作していないが、私は呼び出すときsystemLayoutSizeFittingSize:tableView:cellForRowAtIndexPath:でていることを使用、その後、その後、結果をキャッシュし、tableView:heightForRowAtIndexPath:それ限り制約が正しくもちろん設定されているとしてうまく機能!
アリは2014年

あなたがdequeueReusableCellWithIdentifierを使用する場合にのみ機能します。代わりにdequeueReusableCellWithIdentifierの:forIndexPath:
アントワーヌ・

1
tableView:cellForRowAtIndexPath:を直接呼び出すのは良い方法ではないと思います。
イタチ

19

(下部にあるXcode 8.x / Xcode 9.xの場合)

Xcode 7.xの次の問題に注意してください。これは混乱の原因となる可能性があります。

Interface Builderはセルの自動サイズ設定を適切に処理しません。制約が完全に有効であっても、IBは文句を言い、混乱する提案やエラーを提供します。その理由は、IBが制約の指示に応じて行の高さを変更することを望まないためです(セルがコンテンツの周囲に収まるようにするため)。代わりに、行の高さを固定したままにし、制約を変更するよう提案し始めます。これは無視してください

たとえば、すべてを正常に設定し、警告もエラーもない、すべてが機能することを想像してみてください。

ここに画像の説明を入力してください

フォントサイズを変更すると(この例では、説明ラベルのフォントサイズを17.0から18.0に変更しています)。

ここに画像の説明を入力してください

フォントサイズが増加したため、ラベルは3行を占有するようになりました(以前は2行を占有していました)。

Interface Builderが期待どおりに動作した場合、新しいラベルの高さに合わせてセルの高さを変更します。ただし、実際に何が起こるかというと、IBは赤い自動レイアウトエラーアイコンを表示し、ハグ/圧縮の優先順位を変更するように提案します。

ここに画像の説明を入力してください

これらの警告は無視してください。代わりに*できることは、行の高さを手動で変更することです([セル]> [サイズインスペクター]> [行の高さ]を選択)。

ここに画像の説明を入力してください

赤い矢印のエラーが消えるまで、この高さを一度に1クリックずつ(上下ステッパーを使用して)変更していました。(実際には黄色の警告が表示されます。この時点で先に進んで「フレームの更新」を実行すると、すべてが機能するはずです)。

*実際には、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が必須(1000)に設定されている場合でも、Interface Builderは(ラベルの周囲に合うようにセルの高さをサイズ変更する代わりに)セルに合うようにラベルを引き伸ばしたりクリップしたりすることがあります。そのような場合、オートレイアウトの警告やエラーも表示されない可能性があります。または、上記のXcode 7.xとまったく同じように動作することもあります。


こんにちは、動的なセルの内容を持つセルのテーブルビューを持つセルに動的な高さを与えることは可能ですか?
Jignesh B 2017

18

行の高さと推定行の高さの自動寸法を設定するには、次の手順に従って、自動寸法がセル/行の高さのレイアウトに効果的であることを確認します。

  • tableview dataSourceとデリゲートを割り当てて実装する
  • UITableViewAutomaticDimensionrowHeightと見積もられたRowHeightに割り当て
  • delegate / dataSourceメソッドを実装する(つまり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)が複数ある場合は、そのコンテンツサイズに応じて調整する必要があります。より高い優先度で展開/圧縮するラベルの「コンテンツハグと圧縮耐性の優先度」を調整します。


1
明確でシンプルな解決策のようですが、ラベルではなくテキストビューを使用しているという問題があるため、データが追加されるにつれて行の高さを増やす必要があります。私の問題は、情報をheightForRowAtに渡すことです。変化するテキストビューの高さを測定できますが、行の高さを変更する必要があります。いくつかの助けをお願いします
ジェレミー・アンドリュース

@JeremyAndrews確かにお手伝いします。試したソースコードと問題の詳細を使って質問をしてください。
Krunal 2017年

tableView: heightForRowデータソースを実装しなくても機能します。
Hemang

@Hemang-iOS 11以降、なしで動作しtableView: heightForRowます。(IOSのため10- tableView: heightForRow必要)
Krunal

1
@Hemang-ソリューションは正確なクエリ定義に依存します。ここでは、クエリの一般的な一般的な解決策を示します。内部に条件を置くtableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
Krunal

16

同様ボブ-Spryn @私は答えとしてこれを掲示していていることが重要十分な落とし穴に走りました。

@smileyborgの回答にしばらく苦労しました。あなたは追加要素(とIBであなたのプロトタイプのセルを定義した場合、私はに走ったことを知っておかなけれはUILabelsUIButtonsあなたが[とセルインスタンス化するときIBで、など)[YourTableViewCellClass alloc] init]あなたがいない限り、それはそのセル内の他のすべての要素をインスタンス化しませんがそれを行うために書かれたコード。(私も同様の経験をしましたinitWithStyle。)

ストーリーボードに追加の要素をすべてインスタンス化させると、セルが取得されます[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"][tableView dequeueReusableCellWithIdentifier:forIndexPath:]これは興味深い問題を引き起こすわけではありません)。これを行うと、IBで定義したすべての要素がインスタンス化されます。


15

動的テーブルビューのセルの高さと自動レイアウト

ストーリーボードの自動レイアウトで問題を解決する良い方法:

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

1
これは、この質問に対する受け入れられた回答ですでに広くカバーされています。
スマイリーボー

2
はい、わかっています...しかし、PureLayoutを使いたくなかったので、dispatch_once 'trick'はStoryboardだけを使用して解決するのに大いに役立ちました。
Mohnasm 2014

13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

ここに画像の説明を入力してください


13

別の「解決策」:このフラストレーションをすべてスキップし、代わりにUIScrollViewを使用して、UITableViewと同じ外観と感じの結果を取得します。

それは、スマイリーボーが提案したようなものを構築するために文字通り合計で20以上の非常に苛立たしい時間を費やし、何ヶ月にもわたって失敗し、3バージョンの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ファイルとコントローラーを作成します。UITableViewCellではなくUIViewを使用します。

  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時間以上を無駄にする前に私の投稿を読んでくれることを期待しています。:)


11

動的ビュー(セットアップビューとコードによる制約)を使用する必要があり、preferredMaxLayoutWidthラベルの幅を0に設定したいので、セルの高さが間違っていました。

次に追加しました

[cell layoutSubviews];

実行する前に

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

その後、ラベルの幅は予想どおりになり、動的な高さは正しく計算されていました。


9

サブビューを持つセルがあり、セルの高さをサブビュー+パディングを囲むのに十分な高さにしたいとします。

1)サブビューの下部制約をcell.contentViewから必要なパディングを引いた値に設定します。セルまたはcell.contentView自体に制約を設定しないでください。

2)tableViewのrowHeightプロパティまたはtableView:heightForRowAtIndexPath:に設定しますUITableViewAutomaticDimension

3)tableViewのestimatedRowHeightプロパティまたはtableView:estimatedHeightForRowAtIndexPath:高さの最適な推定値のいずれかに設定します。

それでおしまい。


7

プログラムでレイアウトする場合、iOS 10でSwiftのアンカーを使用する際の考慮事項を次に示します。

3つのルール/ステップがあります

NUMBER 1:viewDidLoadでtableviewのこの2つのプロパティを設定します。1つ目は、セルの動的サイズを期待する必要があるtableviewに伝えます。2つ目は、アプリにスクロールバーインジケーターのサイズを計算させるだけなので、パフォーマンス。

    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 {
    }

これを行うと、実装がオーバーライドされます。

テーブルビューの動的セルについては、この3つのルールに従います。

これは動作中の実装です https://github.com/jamesrochabrun/MinimalViewController


Swift 5でUITableViewAutomaticDimensionの名前がUITableView.automaticDimensionに変更されました
James Rochabrun

4

長い文字列がある場合。たとえば、改行がないもの。次に、いくつかの問題が発生する可能性があります。

「疑わしい」修正は、受け入れられた回答と他のいくつかの回答で言及されています。追加するだけです

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. テキストの長さ:テキストが長い場合、intrinsicContentSizeのが増加します
  2. 改行:追加した\n場合、intrinsicContentSizeの幅はすべての行の最大幅になります。1行が25文字、別の行が2文字、別の行が21文字の場合、幅は25文字に基づいて計算されます
  3. 許可される行数:あなたが設定しなければなりませんnumberOfLines0そうでなければ、複数行を持っていません。あなたnumberOfLinesはintrinsicContentSizeの高さを調整します
  4. 調整を行う:テキストに基づいて、intrinsicContentSizeの幅200と高さはだったが100、幅をラベルのコンテナーに制限したいとしたらどうしますか?解決策は、それを望ましい幅に設定することです。これをに設定preferredMaxLayoutWidthすることで130、新しい組み込みコンテンツサイズはおおよその幅になります130100より多くの行が必要になるため、高さは明らかに高くなります。制約が正しく設定されていれば、これを使用する必要はまったくありません!詳細については、この回答とそのコメントを参照してください。preferredMaxLayoutWidth幅/高さを制限する制約がない場合にのみ使用する必要があります。たとえば、「テキストを超えない限り、テキストを折り返さないでください。preferredMaxLayoutWidth」。しかし、あなたがリード/末尾と設定した場合、100%確実にnumberOfLinesにに0あなたがしている良い!ロングストーリーが短い、それを使用することをお勧めしますここで最も答えは間違っている!あなたはそれを必要としない。それを必要とする、あなたの制約というサインです正しく設定されていない、または制約がないこと

  5. フォントサイズ: fontSizeを大きくすると、intrinsicContentSizeの高さが大きくなることにも注意してください。私のコードではそれを示しませんでした。自分で試すことができます。

したがって、tableViewCellの例に戻ります。

あなたがする必要があるのは:

  • セットnumberOfLinesには0
  • ラベルをマージン/エッジに正しく拘束する
  • 設定する必要はありませんpreferredMaxLayoutWidth

1

私の場合、サーバーから取得された画像を使用してカスタムのセルを作成する必要があり、幅と高さは任意です。そして、動的サイズ(幅と高さの両方)を持つ2つのUILabels

ここで私のautolayoutとプログラムで同じ答えを達成しました:

基本的には@smileyBorgの回答は役に立ちましたが、systemLayoutSizeFittingSizeは私にとってはうまくいきませんでした。私のアプローチでは:

1.自動行高さ計算プロパティを使用しません。2.推定された高さの使用なし3.不要なupdateConstraintsの必要なし。4.自動優先最大レイアウト幅は使用しません。5. systemLayoutSizeFittingSizeは使用しません(使用する必要がありますが、機能しません。内部で何が行われているのかわかりません)。代わりに、メソッド-(float)getViewHeightが機能し、内部で何が行われているのかを確認します。

セルを表示するいくつかの異なる方法を使用すると、UITableViewセルで異なる高さを持つことができますか?


1

私の場合、パディングはsectionHeaderとsectionFooterの高さが原因でしたが、ストーリーボードではそれを最小1に変更できました。viewDidLoadメソッドで:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

1

私は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

1

@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:はテーブルビューの幅です。


0

これらの2つの関数をビューコントローラーに追加するだけで、問題が解決します。ここで、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
}

-1
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)

    }

-1

セルの高さがコンテンツによって動的である場合は、正確に数え、セルがレンダリングされる前に高さの値を返す必要があります。簡単な方法は、テーブルセルの高さのデリゲートメソッドで呼び出すコントローラーのテーブルビューのセルコードでカウント方法を定義することです。高さがテーブルまたは画面の幅に依存している場合は、実際のセルフレームの幅(デフォルトは320)をカウントすることを忘れないでください。つまり、テーブルセルの高さのデリゲートメソッドで、cell.frameを使用してセルの幅を修正してから、セルで定義されている高さのカウントメソッドを呼び出して適切な値を取得し、それを返します

PS。セルオブジェクトを生成するためのコードは、別のテーブルビューのセルデリゲートメソッドが呼び出す別のメソッドで定義できます。


-3

UITableView.automaticDimension Interface Builderで設定できます:

Xcode>ストーリーボード>サイズインスペクター

テーブルビューのセル>行の高さ> 自動

サイズインスペクター


-4

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

注:systemLayoutSizeFittingSizeが私の場合機能しません
djdance

のセルの高さが正しくありませんcellForRowAtIndexPath。この時点ではセルはまだレイアウトされていません。
Andrii Chernenko 2016

iOs7では固定値、つまり機能します。必要に応じてcellForRowAtIndexPathの外部で設定できます
djdance
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.