web-dev-qa-db-ja.com

UITableViewの動的なセルの高さは、スクロール後にのみ修正されます

自動レイアウトを使用して、ストーリーボードで定義されたカスタム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;
}

私が見逃している可能性のあるものがありますか?それは自動レイアウトが最初の数回その仕事をすることができないようにしていますか?

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

Sample project: Top of table view from sample project on first load.Sample project: Same cells after scrolling down and back up.

98
blackp

これが明確に文書化されているかどうかはわかりませんが、セルを返す前に[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;
}
125
rintaro

これは、他の同様のソリューションではうまくいかなかったときにうまくいきました。

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

AutoLayoutとUITableViewAutomaticDimensionの使用方法に非常に精通しているため、これは実際のバグのように見えますが、この問題に時々出くわします。回避策として機能するものをようやく見つけたことがうれしいです。

34

cellForRowAtIndexPath[cell layoutIfNeeded]を追加しても、最初に表示範囲外にスクロールされるセルでは機能しません。

[cell setNeedsLayout]で始まることもありません。

正しくサイズを変更するには、特定のセルをスクロールして表示に戻す必要があります。

ほとんどの開発者は、この厄介なケースを除いて、Dynamic Type、AutoLayout、およびSelf-Sizing Cellsが適切に機能しているため、これはかなりイライラします。このバグは、すべての「長めの」Table View Controllerに影響します。

20
Scenario

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

なぜ起こるのですか?

ストーリーボードで設計されたセルで、一部のデバイスにある程度の幅があります。たとえば、400px。たとえば、ラベルの幅は同じです。ストーリーボードから読み込むと、幅は400ピクセルになります。

問題があります:

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

そのため、幅400pxのラベルとセルの高さを計算しました。ただし、画面のあるデバイス(320pxなど)で実行します。そして、この自動計算された高さは正しくありません。セルのlayoutSubviewstableView: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;
}
13

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

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

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

これは、一部の人々にとって、layoutSubviewsにreloadDataを入れる理由を説明し、問題を解決します

9
alvinmeimoun

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

5
Claus

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

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

UIView.performWithoutAnimation呼び出しが必要です。それ以外の場合は、View Controllerのロード時に通常のテーブルビューアニメーションが表示されます。

4
Chris Vig

テーブルビューのカスタムセル内のすべてのコンテンツに制約を追加し、テーブルビューの行の高さを推定し、viewdidロードで行の高さを自動ディメンションに設定します。

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

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

class CustomTableViewCell: UITableViewCell {

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

このページのすべてのソリューションを試しましたが、使用サイズクラスのチェックを外してから再度チェックすると、問題が解決しました。

編集:サイズクラスのチェックを外すと、ストーリーボードで多くの問題が発生するため、別の解決策を試しました。 View ControllerのviewDidLoadメソッドとviewWillAppearメソッドにTable Viewを追加しました。これで問題が解決しました。

2
ACengiz

Attatching screenshot for your referance 私にとってこれらのアプローチはどれも機能しませんでしたが、Interface Builderでラベルに明示的なPreferred Widthが設定されていることがわかりました。これを削除して(「明示的」のチェックを外して)UITableViewAutomaticDimensionを使用すると、期待どおりに機能しました。

2
Jack James

preferredMaxLayoutWidthを設定すると、私の場合に役立ちます。追加した

cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width

私のコードで。

ILabelでは1行のテキストに2行かかる および http://openradar.appspot.com/17799811 も参照してください。

2
Xhacker Liu

cell.layoutIfNeeded()cellForRowAt内で呼び出すと、ios 10およびios 11で機能しましたが、ios 9では機能しませんでした。

iOS 9でもこの機能を実現するために、cell.layoutSubviews()を呼び出しましたが、うまくいきました。

2
mnemonic23

私の場合、他のサイクルで更新していました。したがって、labelViewの設定後に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
     // }
}
1
Den Jo

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

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

tableViewデータはWebサービスから読み込まれ、接続のコールバックで上記の行を記述します。

1
JAHelia

ラベルのサイズ変更に問題がありますので、
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
Svitlana

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

どういたしまして :)

1
Pranav Rivankar

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

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

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
Naloiko Eugene

まだこの問題に直面している人は、この魔法のラインを試してください

  tableview.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)

テーブルをデータで埋めた後にこれを呼び出すようにしてください。そうすれば、セクション0と行0が常に使用可能であることを確認できます。そうでない場合はクラッシュします。

0
IsPha

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

[self.tableView reloadData];

やってみた

[cell layoutIfNeeded];

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

0
Chris Prince

私の場合、セル内のスタックビューが問題の原因でした。明らかにバグです。削除すると、問題は解決しました。

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


}

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

0
Ramesh Muthe

上記のソリューションはいずれも機能しませんでしたが、次の提案の組み合わせは機能しました。

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() 
0

この問題にぶつかり、ビュー/ラベルの初期化コードをtableView(willDisplay cell:)からtableView(cellForRowAt:)に移動して修正しました。

0
raisedandglazed