1582

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

4

28 に答える 28

2459

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

概念的な説明

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

1. 制約の設定と追加

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

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

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

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

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

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

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

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

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

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

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

iOS 8 の場合 - 自己サイジング セル

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

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

Apple:セルフサイズ テーブル ビュー セルの操作

iOS 8 では、Apple は、iOS 8 より前にユーザーが実装する必要があった作業の多くを内部化しました。セルフサイズのセル メカニズムを機能させるには、最初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 が調整されます)。

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

iOS 7 のサポート (セルの自動サイズ変更を自分で実装する)

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

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

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

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

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

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

一般的に言えば、提供する見積もりはそれほど正確である必要はありません。テーブル ビューのスクロール インジケーターのサイズを正しくするためにのみ使用されます。画面上のセルをスクロールします。estimatedRowHeightテーブルビューのプロパティを(viewDidLoadまたは同様の)「平均」行の高さである定数値に設定する必要があります。行の高さに極端な変動があり (たとえば、桁違いに異なる)、スクロールするときにスクロール インジケーターが「ジャンプ」していることに気付いた場合にのみ、各行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によってまとめられたこのサンプル プロジェクトを確認してください。

于 2013-09-11T16:48:39.377 に答える
60

これが私の解決策です:

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

Objective-C

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

Swift 4.2へのアップデート

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}
于 2015-02-10T19:41:26.063 に答える
47

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

そのためには、カスタム 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の回答をこれと混ぜるとうまくいくはずです。

于 2013-11-11T16:52:47.730 に答える
25

答えとして投稿するのに出くわしただけの十分に重要な落とし穴。

@smileyborgの答えはほとんど正しいです。ただし、layoutSubviewsカスタム セル クラスのメソッドにコードがある場合 (たとえば、 を設定する場合preferredMaxLayoutWidth)、このコードでは実行されません。

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

それはしばらくの間私を混乱させました。contentView次に、セル自体ではなく、レイアウトサブビューのみをトリガーしているためであることに気付きました。

私の作業コードは次のようになります。

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

setNeedsLayout新しいセルを作成している場合は、既に設定されているはずなので、呼び出す必要はないと確信しています。セルへの参照を保存する場合は、おそらくそれを呼び出す必要があります。いずれにせよ、それは何も傷つけないはずです。

のようなものを設定しているセルサブクラスを使用している場合の別のヒントpreferredMaxLayoutWidth。@smileyborg が言及しているように、「テーブル ビュー セルの幅はまだテーブル ビューの幅に固定されていません」。これは真実であり、View Controller ではなくサブクラスで作業を行っている場合に問題が発生します。ただし、テーブル幅を使用して、この時点でセル フレームを簡単に設定できます。

たとえば、高さの計算では次のようになります。

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

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

于 2013-12-03T02:23:31.073 に答える
20

(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 は、(ラベルの周囲に収まるようにセルの高さをリサイズするのではなく) セルに合わせてラベルを拡大またはクリップする場合があります。そのような場合、AutoLayout の警告やエラーが表示されないことさえあります。または、上記で説明した Xcode 7.x とまったく同じことを行う場合もあります。

于 2016-06-27T22:49:56.407 に答える
19

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

于 2014-03-05T15:53:36.960 に答える
19

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

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

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

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

于 2014-04-29T18:16:45.197 に答える
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;
}
于 2014-08-22T10:14:19.613 に答える
13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

ここに画像の説明を入力

于 2016-10-30T12:43:50.287 に答える
13

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

これは、スマイリーボーグが提案したようなものを構築しようとして合計 20 時間以上の非常に苛立たしい時間を費やし、何ヶ月にもわたって App Store リリースの 3 つのバージョンに失敗した後、私にとって苦痛な「解決策」でした。

私の意見では、本当に iOS 7 のサポートが必要な場合 (私たちにとっては不可欠です)、そのテクノロジーはあまりにも脆弱であり、試してみると頭がいっぱいになるでしょう。また、その UITableView は、高度な行編集機能の一部を使用している場合や、1000 以上の「行」を実際にサポートする必要がない限り、一般的に完全にやり過ぎです (このアプリでは、現実的には 20 行を超えることはありません)。

追加のボーナスは、UITableView に付属するすべてのデリゲートのがらくたと前後に比べて、コードが非常にシンプルになることです。これは、見た目がエレガントで管理しやすい viewOnLoad 内の 1 つのコード ループにすぎません。

これを行う方法に関するいくつかのヒントを次に示します。

  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 は、コンテンツが全画面表示にならない場合、コンテンツの下にいくつかの空の行と仕切りを表示しますが、このソリューションはそうではありません。しかし、個人的には、それらの空の行がとにかくそこにない方が好きです-セルの高さが可変であるため、そこに空の行があると、とにかく「バグがある」ように見えました。

他のプログラマーが、自分のアプリで Table View を使用して 20 時間以上無駄に時間を費やす前に、私の投稿を読んでくれることを願っています。:)

于 2015-06-14T22:17:20.627 に答える
11

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

それから私は追加しました

[cell layoutSubviews];

実行前

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

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

于 2015-09-01T22:37:52.867 に答える
1

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

ここで、自動レイアウトとプログラムで同じことを回答で達成しました:

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

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

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

于 2016-05-27T06:12:15.393 に答える
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
于 2017-11-25T01:56:43.623 に答える
-1

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

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

于 2019-09-24T16:37:33.237 に答える
-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
    }
}
于 2016-01-16T15:13:49.643 に答える