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

2013年09月12日に質問されました。  ·  閲覧回数 602.7k回  ·  ソース

smileyborg picture
2013年09月12日

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

回答

smileyborg picture
2013年09月12日
2416

TL; DR:読むのが好きではありませんか? GitHubのサンプルプロジェクトに直接ジャンプします。

概念の説明

以下の最初の2つの手順は、開発しているiOSのバージョンに関係なく適用できます。

1.制約の設定と追加

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

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

Example illustration of constraints on a table view cell.

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

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

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

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

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

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

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

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

iOS8の場合-セルのセルフサイジング

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が必要な高さを各セルに自動的に尋ねます。セクションインデックスやアクセサリビューなどの追加事項)、およびセルのコンテンツビューとサブビューに追加した自動レイアウト制約。 この実際のセルの高さが決定されると、行の古い推定高さが新しい実際の高さで更新されます(必要に応じて、テーブルビューのcontentSize / contentOffsetが調整されます)。

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

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

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

まず、テーブルビューセルのオフスクリーンインスタンスをインスタンス化します。再利用識別子ごとtableView:cellForRowAtIndexPath:から返されることはないことを意味します。)次に、セルを正確なコンテンツで構成する必要があります(例:テーブルビューに表示された場合に保持されるテキスト、画像など)。

次に、セルにサブビューをすぐにレイアウトするように強制し、 UITableViewCellcontentView systemLayoutSizeFittingSize:メソッドを使用して、セルの必要な高さを調べます。 UILayoutFittingCompressedSizeを使用して、セルのすべてのコンテンツに適合するために必要な最小サイズを取得します。 高さは、 tableView:heightForRowAtIndexPath:デリゲートメソッドから返すことができます。

4.推定行高さを使用します

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

iOS 7以降、テーブルビューで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によってサンプルプロジェクトを確認してください。

William Hu picture
2015年06月19日
179

上記のiOS8の場合、これは非常に簡単です。

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
}

重要

  • 複数行のラベルがある場合は、 numberOfLines0設定することを忘れないでください。

  • label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)忘れないでください

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

Suragch picture
2016年04月05日
97

可変高UITableViewCellの迅速な例

Swift3用に更新

William HuのSwiftの答えは良いですが、初めて何かをすることを学ぶときに、いくつかの簡単で詳細な手順を踏むのに役立ちます。 以下の例は、セルの高さが可変のUITableView作成を学習しているときの私のテストプロジェクトです。 私はSwiftのこの基本的なUITableViewの例に基づいてい

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

enter image description here

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

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

コードを追加する

プロジェクトに新しい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
    

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

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

enter image description here

重要な注意点:

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

その他のIB設定

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

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

enter image description here

ラベルのゼロライン

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

enter image description here

アウトレットを接続する

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

終了しました

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

ノート

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

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

も参照してください

Adam Waite picture
2014年06月11日
65

@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でこのジャズを行う必要はありませんが、今のところあります。

Eddwin Paz picture
2015年02月11日
57

これが私の解決策です:

ビューをロードする前に、 TableViewestimatedHeightを通知する必要があります。 そうしないと、期待どおりに動作できません。

Objective-C

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}

スウィフト4.2へのアップデート

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}
wildmonkey picture
2013年11月12日
47

@smileyborgによって提案された解決策はほぼ完璧です。 カスタムセルがあり、動的な高さの1つ以上のUILabelが必要な場合、 AutoLayoutを有効にしたCGSizeZero返します。 (@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の回答とこれを組み合わせるとうまくいくはずです。

Bob Spryn picture
2013年12月03日
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ようなものを設定しているセルサブクラスを使用している場合のもう1つのヒント。 @smileyborgが言及しているように、「テーブルビューセルの幅はまだテーブルビューの幅に固定されていません」。 これは真実であり、ViewControllerではなくサブクラスで作業を行っている場合は問題が発生します。 ただし、テーブル幅を使用して、この時点でセルフレームを設定するだけです。

たとえば、高さの計算では:

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

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

Alex Rouse picture
2014年03月05日
20

人々がまだこれに問題を抱えている場合に備えて。 動的セルの高さの活用するUITableViewsでの自動レイアウトの使用と、これをより抽象的で実装しやすくするためのオープンソースコンポーネントについての簡単なブログ投稿を書きました。 https://github.com/Raizlabs/RZCellSizeManager

Chris Van Buskirk picture
2014年04月30日
19

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

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

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

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

Nikolay Suvandzhiev picture
2016年06月28日
19

(Xcode 8.x / Xcode 9.xの場合は下部にあります)

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

Interface Builderは、自動サイズ設定セルのセットアップを適切に処理しません。 制約が絶対的に有効であっても、IBは文句を言い、紛らわしい提案やエラーを出します。 その理由は、制約が指示するように(セルがコンテンツの周囲に収まるように)、IBが行の高さを変更することを望まないためです。 代わりに、行の高さを固定し、制約の変更を提案し始めますが、無視する必要があり

たとえば、すべてが正常に設定され、警告もエラーも発生せず、すべてが機能するとします。

enter image description here

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

enter image description here

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

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

enter image description here

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

enter image description here

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

* Interface Builderでこれらの赤いエラーや黄色の警告を実際に解決する必要はないことに注意してください-実行時に、すべてが正しく機能します(IBがエラー/警告を表示している場合でも)。 実行時にコンソールログでAutoLayoutエラーが発生していないことを確認してください。

実際、IBで行の高さを常に更新しようとすると、非常に煩わしく、場合によっては不可能に近くなります(小数値のため)。

迷惑なIB警告/エラーを防ぐために、関連するビューを選択し、プロパティAmbiguity Size InspectorVerify Position Only選択できます。

enter image description here


Xcode 8.x / Xcode 9.xは(時々)Xcode 7.xとは異なる動作をしているように見えますが、それでも正しくありません。 たとえば、 compression resistance priority / hugging priorityがrequired(1000)に設定されている場合でも、Interface Builderは、セルの高さをラベルの周囲に合わせてサイズ変更する代わりに、ラベルをストレッチまたはクリップしてセルに合わせることができます。 そして、そのような場合、AutoLayoutの警告やエラーさえ表示されない可能性があります。 または、上記のXcode7.xとまったく同じように動作する場合もあります。