UITableViewの動的セルの高さは、スクロールした後でのみ正しくなる


117

私が持っているUITableViewカスタムでUITableViewCell自動レイアウトを使用して、ストーリーボードで定義されています。セルには複数の行がありUILabelsます。

UITableViewセルの高さを適切に計算しているように見えますが、最初のいくつかのセルでは、高さがラベル間で適切に分割されていません。少しスクロールした後、すべてが期待どおりに機能します(最初は正しくなかったセルも)。

- (void)viewDidLoad {
    [super viewDidLoad]
    // ...
    self.tableView.rowHeight = UITableViewAutomaticDimension;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
    // ...
    // Set label.text for variable length string.
    return cell;
}

自動レイアウトが最初の数回その仕事をすることができない原因となっている、私が見逃しているかもしれないものはありますか?

この動作を示すサンプルプロジェクトを作成しました。

サンプルプロジェクト:最初の読み込み時のサンプルプロジェクトのテーブルビューの上部。 サンプルプロジェクト:下にスクロールしてバックアップした後の同じセル。


1
また、私はこの問題に直面しています、しかし、以下の答えは私のために働いていない、この上の任意のヘルプ
Shangari C

1
同じ問題に直面してセルにlayoutIfNeededを追加してもうまくいきませんか?その他の提案
Max

1
素晴らしいスポット。これは2019年の問題です:/
Fattie

私は次のリンクで私を助けたものを見つけました-これは可変高さのテーブルビューセルのかなり包括的な議論です:stackoverflow.com/a/18746930/826946
Andy Weinstein

回答:


139

これが明確に文書化されているかどうかはわかりませんが、[cell layoutIfNeeded]セルを返す前に追加すると問題が解決します。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
    NSUInteger n1 = firstLabelWordCount[indexPath.row];
    NSUInteger n2 = secondLabelWordCount[indexPath.row];
    [cell setNumberOfWordsForFirstLabel:n1 secondLabel:n2];

    [cell layoutIfNeeded]; // <- added

    return cell;
}

3
しかし、なぜそれが最初だけ必要なのですか?-layoutIfNeededセルのに呼び出しを入れても同様に機能し-awakeFromNibます。layoutIfNeeded必要な理由がわかっている場合にのみ電話をかけたいと思います。
blackp '23

2
私のために働いた!そして、なぜそれが必要なのか知りたいです!
Mustafa

サイズクラスをレンダリングする場合は機能しませんでした。Xクラスとは異なります
Andrei Konstantinov

@AndreyKonstantinovうん、サイズクラスでは機能しません。サイズクラスで機能させるにはどうすればよいですか。
z22 2015年

サイズクラスなしで機能しますが、サイズクラスのソリューションはありますか?
Yuvrajsinh

38

他の同様の解決策がうまくいかなかったときにこれは私のために働きました:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    layoutIfNeeded()
}

AutoLayoutとUITableViewAutomaticDimensionの使用方法に精通しているため、これは実際のバグのようですが、まだこの問題に遭遇することがあります。ようやく回避策として機能するものを見つけてよかったです。


1
セルがすべてのセル表示ではなくxibからロードされているときにのみ呼び出されるため、受け入れられた回答よりも優れています。コード行もはるかに少なくなります。
ロバートワグスタッフ2017年

3
電話することを忘れないでくださいsuper.didMoveToSuperview()
cicerocamargo '28

@cicerocamargo Appleは次のように述べていdidMoveToSuperviewます:「このメソッドのデフォルト実装は何もしません。」
Ivan Smetanin、2018年

4
@IvanSmetaninデフォルトの実装で何も実行されなくても問題ありません。将来、実際に何かが実行される可能性があるため、スーパーメソッドを呼び出す必要があります。
TNguyen

(ただ、その上でDEFINITELYをDEFINITELYで呼び出す必要があります。チーム全体が一度だけセルをサブクラス化しないプロジェクトを見たことがないので、もちろん、それらすべてを確実にピックアップする必要があります。 TNguyenの発言とは別の問題です。)
Fattie

22

追加[cell layoutIfNeeded]するcellForRowAtIndexPath最初にスクロールしてビューの外にあるセルでは、機能しません。

また、前に [cell setNeedsLayout]

それらを正しくサイズ変更するには、特定のセルをスクロールアウトしてビューに戻す必要があります。

この厄介なケースを除いて、ほとんどの開発者はダイナミックタイプ、オートレイアウト、セルのサイズ変更セルが適切に機能しているため、これはかなりイライラします。このバグは、私の「背の高い」テーブルビューコントローラすべてに影響します。


わたしも。最初にスクロールしたとき、セルのサイズは44pxです。スクロールしてセルを見えなくして戻ってきた場合、適切なサイズになっています。解決策は見つかりましたか?
最大

@ ashy_32bit、ありがとうございました。これで解決されました。
ImpurestClub

@Scenario、それは明白なバグのように思われますか?恐ろしいもの。
Fattie

3
[cell layoutSubviews]layoutIfNeededの代わりに使用すると、修正が可能になる場合があります。stackoverflow.com/a/33515872/1474113
yprestoを

14

私は自分のプロジェクトの1つで同じ経験をしました。

なぜそれが起こるのですか?

ストーリーボードで設計されたセルで、デバイスによっては幅があります。たとえば400px。たとえば、ラベルの幅は同じです。ストーリーボードからロードすると、幅は400pxになります。

ここに問題があります:

tableView:heightForRowAtIndexPath: セルレイアウトの前にサブビューとして呼び出されます。

したがって、幅400pxのラベルとセルの高さを計算しました。ただし、画面が320pxなどのデバイスで実行します。そして、この自動的に計算された高さは正しくありません。セルがlayoutSubviews発生するのは、ラベルを手動でtableView:heightForRowAtIndexPath: 設定preferredMaxLayoutWidthした後でも発生しないからとlayoutSubviewsいって、役に立ちません。

私の解決策:

1)サブクラス化UITableViewしてオーバーライドしますdequeueReusableCellWithIdentifier:forIndexPath:。セルの幅をテーブルの幅に等しく設定し、セルのレイアウトを強制します。

- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [super dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
    CGRect cellFrame = cell.frame;
    cellFrame.size.width = self.frame.size.width;
    cell.frame = cellFrame;
    [cell layoutIfNeeded];
    return cell;
}

2)サブクラスUITableViewCellpreferredMaxLayoutWidthでラベルを手動で設定しますlayoutSubviews。またcontentView、セルフレームの変更後に自動的にレイアウトされないため、手動でレイアウトする必要があります(理由はわかりませんが、実際に行われます)。

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.contentView layoutIfNeeded];
    self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}

1
この問題が発生したときは、テーブルビューのフレームが正しいことも確認する必要があったので、(viewDidLoadで)テーブルビューにデータを入力する前に[self.tableview layoutIfNeeded]を呼び出しました。
GK100 2016年

私はそれが間違った幅と関係があるはずだと思って止まらなかった。cell.frame.size.width = tableview.frame.widthその後cell.layoutIfNeeded()cellForRowAt関数で私のためにトリックをしました
Cam Connor

10

同様の問題があります。最初のロードでは、行の高さが計算されませんでしたが、スクロールしたり別の画面に移動したりすると、この画面に戻って行が計算されます。最初の読み込みでアイテムがインターネットから読み込まれ、2番目の読み込みでアイテムが最初にコアデータから読み込まれ、インターネットから再読み込みされました。インターネットからの再読み込みで行の高さが計算されていることに気付きました。そのため、セグエアニメーションの実行中にtableView.reloadData()が呼び出されると(プッシュと現在のセグエと同じ問題)、行の高さが計算されないことに気付きました。したがって、ビューの初期化時にtableviewを非表示にして、ユーザーへの醜い影響を防ぐためにアクティビティローダーを配置し、300ミリ秒後にtableView.reloadDataを呼び出して問題を解決しました。UIKitのバグだと思いますが、この回避策でうまくいきます。

これらの行(Swift 3.0)をアイテム読み込み完了ハンドラーに入れました

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
        self.tableView.isHidden = false
        self.loader.stopAnimating()
        self.tableView.reloadData()
    })

これは、一部の人にとって、relayDataをlayoutSubviewsに配置して問題を解決する理由を説明しています


これも私のために働いた唯一のワシントン州です。isHiddenは使用しませんが、データソースを追加するときにテーブルのアルファを0に設定し、リロード後に1に設定します。
PJ_Finnegan 2017年

6

上記の解決策はどれも私にとってはうまくいきませんでした、うまくいったのはこの魔法のレシピです:この順序でそれらを呼び出してください:

tableView.reloadData()
tableView.layoutIfNeeded() tableView.beginUpdates() tableView.endUpdates()

私のtableViewデータはWebサービスから入力され、接続のコールバックで上記の行を書き込みます。


1
Swift 5の場合、これはコレクションビューでも機能しました。
Govani Dhruv Vijaykumar

私にとって、これはセルの正しい高さとレイアウトを返しましたが、データはありませんでした
Darrow Hartman

これらの行の後でsetNeedsDisplay()を試してください
JAHelia

5

私の場合、セルが初めて表示されたときに、UILabelの最後の行が切り捨てられました。これはかなりランダムに発生し、セルのサイズを正しく設定する唯一の方法は、セルをビューの外にスクロールして戻すことでした。これまでに表示されたすべての可能な解決策(layoutIfNeeded..reloadData)を試しましたが、何もうまくいきませんでした。トリックは、"Autoshrink"Minimuum Font Scale(私にとっては0.5)に設定することでした。試してみる


これは、上記の他のソリューションを適用せずに機能しました。素晴らしい発見。
mckeejm 2015

2
しかし、これによりテキストが自動縮小されます...テーブルビューでさまざまなサイズのテキストが必要なのはなぜですか?ひどいですね。
Lucius degeer 2017

5

テーブルビューのカスタムセル内のすべてのコンテンツに制約を追加し、テーブルビューの行の高さを見積もり、行の高さをviewdid loadでの自動寸法に設定します。

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

初期読み込みの問題を修正するには、カスタムテーブルビューセルでlayoutIfNeededメソッドを使用します。

class CustomTableViewCell: UITableViewCell {

override func awakeFromNib() {
    super.awakeFromNib()
    self.layoutIfNeeded()
    // Initialization code
}
}

estimatedRowHeight0以外の値UITableViewAutomaticDimension(-1)に設定することが重要です。そうしないと、行の自動高さが機能しません。
PJ_Finnegan 2017年

5

私はこの質問に対する回答のほとんどを試しましたが、どれも機能させることができませんでした。私が見つけた唯一の機能的な解決策は、私のUITableViewControllerサブクラスに以下を追加することでした:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    UIView.performWithoutAnimation {
        tableView.beginUpdates()
        tableView.endUpdates()
    }
}

UIView.performWithoutAnimationコールは、そうでない場合は、ビューコントローラの負荷として、通常のテーブルビューのアニメーションが表示され、必要とされます。


正常に機能し、reloadDataを2回呼び出すよりも間違いなく優れています
Jimmy George Thomas

2
黒魔術。それを行うことはviewWillAppear私にとってはうまくいきませんでしたが、それを行うことでうまくいきましviewDidAppearた。
マーティン

このソリューションは、viewDidAppearに実装されたときに機能しました。正しいサイズ設定が行われるとテーブルコンテンツがジャンプするため、ユーザーエクスペリエンスにとって決して最適ではありません。
richardpiazza

驚くべき私はあなたが悪魔ある例の多くを試みたが、失敗してきた
シャキール・アーメド

@martinと同じ
AJヘルナンデス


3

iOS 10とiOS 11ではcell.layoutIfNeeded()内部通話がcellForRowAt機能しましたが、iOS 9では機能しませんでした。

iOS 9でもこの作業を行うために、私は電話をかけましたcell.layoutSubviews()


2

参考のためにスクリーンショットを添付私にとって、これらのアプローチはどれもうまくいきませんでしたが、LabelがPreferred WidthInterface Builderで明示的なセットを持っていることを発見しました。それを削除し(「明示的」のチェックを外す)、使用UITableViewAutomaticDimensionは期待どおりに機能しました。


2

このページのすべての解決策を試しましたが、使用サイズクラスをオフにしてからもう一度チェックすると、問題が解決しました。

編集:サイズクラスをオフにすると、ストーリーボードで多くの問題が発生するため、別の解決策を試しました。私は自分のテーブルビューをビューコントローラーviewDidLoadviewWillAppearメソッドに入力しました。これは私の問題を解決しました。


1

ラベルのサイズ変更に問題があるので
、テキストの設定後にchatTextLabel.text = chatMessage.message chatTextLabel?.updateConstraints()を実行するだけです

//完全なコード

func setContent() {
    chatTextLabel.text = chatMessage.message
    chatTextLabel?.updateConstraints()

    let labelTextWidth = (chatTextLabel?.intrinsicContentSize().width) ?? 0
    let labelTextHeight = chatTextLabel?.intrinsicContentSize().height

    guard labelTextWidth < originWidth && labelTextHeight <= singleLineRowheight else {
      trailingConstraint?.constant = trailingConstant
      return
    }
    trailingConstraint?.constant = trailingConstant + (originWidth - labelTextWidth)

  }

1

私の場合、他のサイクルで更新していました。そのため、labelTextが設定された後にtableViewCellの高さが更新されました。非同期ブロックを削除しました。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     let cell = tableView.dequeueReusableCell(withIdentifier:Identifier, for: indexPath) 
     // Check your cycle if update cycle is same or not
     // DispatchQueue.main.async {
        cell.label.text = nil
     // }
}

非同期ブロックもありますが、必要ですか?代替手段はありませんか?
Nazar Medeiros

1

テーブルビューの 'willdisplaycell'デリゲートメソッドでラベルテキストを設定していないことを確認してください。動的な高さ計算のために、「cellForRowAtindexPath」デリゲートメソッドにラベルテキストを設定します。

どういたしまして :)


1

私の場合、セルのスタックビューが問題の原因でした。どうやらバグです。取り除くと問題は解決しました。


1
私は他のすべてのソリューションを試しましたが、あなたのソリューションはそれを共有してくれてありがとうございます
tamtoum1987

1

問題は、有効な行の高さが得られる前に初期セルが読み込まれることです。回避策は、ビューが表示されたときにテーブルを強制的に再ロードすることです。

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}

これは私を助けたはるかにはるかにはるかに優れた解決策でした。セルの100%は修正されませんでしたが、最大80%が実際のサイズを使用していました。
どうも

1

iOS 12以降のみ、2019以降...

文字通り何年にもわたって問題が続いているAppleのたまに奇妙な無能の進行中の例。

それはそうであるようです

        cell.layoutIfNeeded()
        return cell

それを修正します。(もちろん、一部のパフォーマンスが低下します。)

これがAppleのある生活です。


0

私の場合、セルの高さに関する問題は、最初のテーブルビューが読み込まれた後に発生し、ユーザーアクションが発生します(セルの高さを変更する効果があるセルのボタンをタップします)。私がしない限り、セルに高さを変更させることができませんでした:

[self.tableView reloadData];

私は試しました

[cell layoutIfNeeded];

しかし、それはうまくいきませんでした。


0

Swift 3では、再利用可能なセルのテキストを更新するたびにself.layoutIfNeeded()を呼び出す必要がありました。

import UIKit
import SnapKit

class CommentTableViewCell: UITableViewCell {

    static let reuseIdentifier = "CommentTableViewCell"

    var comment: Comment! {
        didSet {
            textLbl.attributedText = comment.attributedTextToDisplay()
            self.layoutIfNeeded() //This is a fix to make propper automatic dimentions (height).
        }
    }

    internal var textLbl = UILabel()

    override func layoutSubviews() {
        super.layoutSubviews()

        if textLbl.superview == nil {
            textLbl.numberOfLines = 0
            textLbl.lineBreakMode = .byWordWrapping
            self.contentView.addSubview(textLbl)
            textLbl.snp.makeConstraints({ (make) in
                make.left.equalTo(contentView.snp.left).inset(10)
                make.right.equalTo(contentView.snp.right).inset(10)
                make.top.equalTo(contentView.snp.top).inset(10)
                make.bottom.equalTo(contentView.snp.bottom).inset(10)
            })
        }
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let comment = comments[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: CommentTableViewCell.reuseIdentifier, for: indexPath) as! CommentTableViewCell
        cell.selectionStyle = .none
        cell.comment = comment
        return cell
    }

commentsTableView.rowHeight = UITableViewAutomaticDimension
    commentsTableView.estimatedRowHeight = 140

0

上記の解決策はどれもうまくいきませんでしたが、提案の次の組み合わせはうまくいきました。

以下をviewDidLoad()に追加する必要がありました。

DispatchQueue.main.async {

        self.tableView.reloadData()

        self.tableView.setNeedsLayout()
        self.tableView.layoutIfNeeded()

        self.tableView.reloadData()

    }

上記のreloadData、setNeedsLayout、layoutIfNeededの組み合わせは機能しましたが、その他は機能しませんでした。ただし、プロジェクトのセルに固有である可能性があります。そして、はい、それを機能させるにはreloadDataを2回呼び出す必要がありました。

また、viewDidLoadで以下を設定します

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight

tableView(_ tableView:UITableView、cellForRowAt indexPath:IndexPath)の場合

cell.setNeedsLayout()
cell.layoutIfNeeded() 

私の同様のreloadData/beginUpdates/endUpdates/reloadDataものも働いています。reloadData もう一度呼び出す必要あります。ラップする必要はありませんasync
レイ

0

私はこの問題に遭遇し、ビュー/ラベル初期化コードをFROM tableView(willDisplay cell:)TOに移動することで修正しましたtableView(cellForRowAt:)


これは、セルを初期化するための推奨される方法ではありませんwillDisplayはよりもパフォーマンスが向上しますcellForRowAt。正しいセルをインスタンス化するためにのみ、最新のものを使用してください。
マーティン

2年後、私が書き留めた古いコメントを読んでいます。これは悪いアドバイスでした。willDisplayパフォーマンスが向上した場合でもcellForRowAt、レイアウトが自動のときにセルUIを初期化することをお勧めします。実際、レイアウトは cellForRowAtの、willDisplayの前に UIKit によって計算されます。したがって、セルの高さがその内容に依存する場合は、でラベルの内容(またはその他)を初期化しcellForRowAtます。
マーティン

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


//  call the method dynamiclabelHeightForText
}

行の高さを動的に返す上記のメソッドを使用します。そして、使用しているラベルに同じ動的な高さを割り当てます。

-(int)dynamiclabelHeightForText:(NSString *)text :(int)width :(UIFont *)font
{

    CGSize maximumLabelSize = CGSizeMake(width,2500);

    CGSize expectedLabelSize = [text sizeWithFont:font
                                constrainedToSize:maximumLabelSize
                                    lineBreakMode:NSLineBreakByWordWrapping];


    return expectedLabelSize.height;


}

このコードは、ラベルに表示されるテキストの動的な高さを見つけるのに役立ちます。


実際にはRameshです。tableView.estimatedRowHeightおよびtableView.rowHeight = UITableViewAutomaticDimensionプロパティが設定されている限り、これは必要ありません。また、カスタムセルにさまざまなウィジェットとcontentViewに対する適切な制約があることを確認してください。
BonanzaDriver 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.