Ketinggian sel dinamis UITableView hanya benar setelah beberapa pengguliran

117

Saya memiliki UITableViewdengan kustom yang UITableViewCellditentukan di storyboard menggunakan tata letak otomatis. Sel memiliki beberapa multiline UILabels.

The UITableViewmuncul ke ketinggian sel benar menghitung, tapi untuk beberapa sel pertama yang tinggi tidak benar dibagi antara label. Setelah menggulir sedikit, semuanya berfungsi seperti yang diharapkan (bahkan sel yang awalnya salah).

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

Apakah ada sesuatu yang mungkin saya lewatkan, yang menyebabkan tata letak otomatis tidak dapat melakukan tugasnya beberapa kali pertama?

Saya telah membuat proyek contoh yang menunjukkan perilaku ini.

Proyek sampel: Tampilan atas tabel dari proyek sampel pada pemuatan pertama. Proyek sampel: Sel yang sama setelah menggulir ke bawah dan ke atas.

blackp
sumber
1
Saya juga menghadapi masalah ini, Tapi jawaban di bawah ini tidak bekerja untuk saya, Ada bantuan dalam hal ini
Shangari C
1
menghadapi masalah yang sama menambahkan layoutIfNeeded for cell tidak berhasil juga bagi saya? ada lagi saran
Max
1
Tempat yang bagus. Ini masih menjadi masalah di 2019: /
Fattie
Saya menemukan apa yang membantu saya di tautan berikut - ini adalah diskusi yang cukup komprehensif tentang sel tampilan tabel tinggi variabel: stackoverflow.com/a/18746930/826946
Andy Weinstein

Jawaban:

139

Saya tidak tahu ini didokumentasikan dengan jelas atau tidak, tetapi menambahkan [cell layoutIfNeeded]sebelum mengembalikan sel memecahkan masalah Anda.

- (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;
}
rintaro
sumber
3
Saya bertanya-tanya, mengapa ini hanya perlu pertama kali? Ini bekerja dengan baik jika saya melakukan panggilan ke -layoutIfNeededdalam sel -awakeFromNib. Saya lebih suka menelepon layoutIfNeededjika saya tahu mengapa itu perlu.
blackp
2
Bekerja untuk saya! Dan saya ingin sekali mengetahui mengapa itu diperlukan!
Mustafa
Tidak berfungsi jika Anda membuat kelas ukuran, yang berbeda dari X mana pun
Andrei Konstantinov
@AndreyKonstantinov Yup tidak berfungsi dengan kelas ukuran, bagaimana cara membuatnya berfungsi dengan kelas ukuran?
z22
Ia bekerja tanpa kelas ukuran, ada solusi dengan kelas ukuran?
Yuvrajsinh
38

Ini berhasil untuk saya ketika solusi serupa lainnya tidak:

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

Ini sepertinya bug yang sebenarnya karena saya sangat akrab dengan AutoLayout dan cara menggunakan UITableViewAutomaticDimension, namun saya kadang-kadang masih menemukan masalah ini. Saya senang akhirnya menemukan sesuatu yang berfungsi sebagai solusi.

Michael Peterson
sumber
1
Lebih baik daripada jawaban yang diterima karena hanya dipanggil saat sel dimuat dari xib alih-alih setiap tampilan sel. Baris kode juga jauh lebih sedikit.
Robert Wagstaff
3
Jangan lupa untuk menghubungisuper.didMoveToSuperview()
cicerocamargo
@cicerocamargo Apple mengatakan tentang didMoveToSuperview: "Implementasi default metode ini tidak melakukan apa-apa."
Ivan Smetanin
4
@IvanSmetanin Tidak masalah jika implementasi default tidak melakukan apa-apa, Anda tetap harus memanggil metode super karena metode ini sebenarnya dapat melakukan sesuatu di masa mendatang.
TNguyen
(Hanya btw Anda harus JELAS memanggil super. Untuk yang satu itu. Saya belum pernah melihat proyek di mana tim secara keseluruhan tidak membuat subkelas lebih dari satu sel, jadi, tentu saja Anda harus memastikan untuk mengambil semuanya. DAN sebagai pisahkan masalah seperti yang dikatakan TNguyen.)
Fattie
22

Menambahkan [cell layoutIfNeeded]di cellForRowAtIndexPathtidak bekerja untuk sel-sel yang awalnya menggulir out-of-view.

Juga tidak mengawali dengan [cell setNeedsLayout].

Anda masih harus menggulir sel tertentu keluar dan kembali ke tampilan agar ukurannya benar.

Ini cukup membuat frustrasi karena sebagian besar pengembang memiliki Jenis Dinamis, Tata Letak Otomatis, dan Sel Ukuran Mandiri yang berfungsi dengan baik - kecuali untuk kasus yang mengganggu ini. Bug ini memengaruhi semua pengontrol tampilan tabel saya yang "lebih tinggi".

Skenario
sumber
Sama untuk ku. ketika saya pertama kali menggulir, ukuran sel adalah 44px. jika saya menggulir untuk membuat sel tidak terlihat dan kembali, ukurannya benar. Apakah Anda menemukan solusi?
Maks.
Terima kasih @ ashy_32bit ini menyelesaikannya untuk saya juga.
ImpurestClub
sepertinya bug biasa, @Scenario kan? hal yang mengerikan.
Fattie
3
Menggunakan [cell layoutSubviews]alih-alih layoutIfNeeded mungkin bisa diperbaiki. Lihat stackoverflow.com/a/33515872/1474113
ypresto
14

Saya memiliki pengalaman yang sama di salah satu proyek saya.

Mengapa ini terjadi?

Sel dirancang di Storyboard dengan beberapa lebar untuk beberapa perangkat. Misalnya 400px. Misalnya label Anda memiliki lebar yang sama. Saat dimuat dari storyboard, ia memiliki lebar 400px.

Inilah masalahnya:

tableView:heightForRowAtIndexPath: dipanggil sebelum tata letak sel itu subview.

Maka dihitung tinggi label dan sel dengan lebar 400px. Tapi Anda berjalan di perangkat dengan layar, misalnya, 320px. Dan ketinggian yang dihitung secara otomatis ini salah. Hanya karena sel layoutSubviewsterjadi hanya setelah tableView:heightForRowAtIndexPath: Bahkan jika Anda mengatur preferredMaxLayoutWidthlabel Anda secara manual di layoutSubviewsdalamnya tidak membantu.

Solusi saya:

1) Subclass UITableViewdan override dequeueReusableCellWithIdentifier:forIndexPath:. Atur lebar sel sama dengan lebar tabel dan paksa tata letak sel.

- (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) Subkelas UITableViewCell. Setel preferredMaxLayoutWidthsecara manual untuk label Anda di layoutSubviews. Anda juga memerlukan tata letak secara manual contentView, karena tata letak tidak otomatis setelah bingkai sel berubah (saya tidak tahu mengapa, tetapi memang demikian)

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.contentView layoutIfNeeded];
    self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
Vitaliy Gozhenko
sumber
1
Ketika saya mengalami masalah ini, saya juga perlu memastikan frame tableview sudah benar, jadi saya memanggil [self.tableview layoutIfNeeded] sebelum tableview diisi (di viewDidLoad).
GK100
Saya tidak pernah berhenti berpikir itu ada hubungannya dengan lebar yang salah. cell.frame.size.width = tableview.frame.widthkemudian cell.layoutIfNeeded()dalam cellForRowAtfungsi melakukan trik untuk saya
Cam Connor
10

Saya memiliki masalah serupa, pada pemuatan pertama, tinggi baris tidak dihitung tetapi setelah beberapa menggulir atau pergi ke layar lain dan saya kembali ke baris layar ini dihitung. Pada pemuatan pertama barang saya dimuat dari internet dan pada pemuatan kedua barang saya dimuat pertama dari Data Inti dan dimuat ulang dari internet dan saya perhatikan bahwa tinggi baris dihitung saat memuat ulang dari internet. Jadi saya perhatikan bahwa ketika tableView.reloadData () dipanggil selama animasi segue (masalah yang sama dengan push dan present segue), tinggi baris tidak dihitung. Jadi saya menyembunyikan tampilan tabel pada inisialisasi tampilan dan meletakkan pemuat aktivitas untuk mencegah efek buruk bagi pengguna dan saya memanggil tableView.reloadData setelah 300ms dan sekarang masalahnya telah terpecahkan. Saya pikir ini adalah bug UIKit tetapi solusi ini berhasil.

Saya meletakkan baris ini (Swift 3.0) di penangan penyelesaian pemuatan item saya

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

Ini menjelaskan mengapa bagi sebagian orang, meletakkan reloadData di layoutSubviews untuk menyelesaikan masalah

alvinmeimoun.dll
sumber
Ini adalah satu-satunya WA yang berhasil untuk saya juga. Kecuali saya tidak menggunakan isHidden, tetapi setel alfa tabel ke 0 saat menambahkan sumber data, lalu ke 1 setelah memuat ulang.
PJ_Finnegan
6

tidak ada solusi di atas yang berhasil untuk saya, yang berhasil adalah resep ajaib ini: panggil mereka dalam urutan ini:

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

data tableView saya diisi dari layanan web, dalam panggilan kembali koneksi saya menulis baris di atas.

JAHelia
sumber
1
Untuk swift 5 ini berhasil meski dalam tampilan koleksi, tuhan memberkati kamu, bro.
Govani Dhruv Vijaykumar
Bagi saya, ini mengembalikan tinggi dan tata letak sel yang benar, tetapi tidak ada data di dalamnya
Darrow Hartman
Coba setNeedsDisplay () setelah baris tersebut
JAHelia
5

Dalam kasus saya, baris terakhir UILabel terpotong saat sel ditampilkan untuk pertama kalinya. Itu terjadi secara acak dan satu-satunya cara untuk mengukurnya dengan benar adalah dengan menggulir sel keluar dari tampilan dan membawanya kembali. Saya mencoba semua solusi yang mungkin ditampilkan sejauh ini (layoutIfNeeded..reloadData) tetapi tidak ada yang berhasil untuk saya. Triknya adalah dengan mengatur "Autoshrink" ke Skala Font Minimuum (0,5 untuk saya). Cobalah

Claus
sumber
ini berhasil untuk saya, tanpa menerapkan solusi lain yang tercantum di atas. Great ditemukan.
mckeejm
2
Tapi ini otomatis mengecilkan teks ... mengapa seseorang ingin memiliki ukuran teks yang berbeda dalam tampilan tabel? Tampak mengerikan.
lucius degeer
5

Tambahkan batasan untuk semua konten dalam sel khusus tampilan tabel, lalu perkirakan tinggi baris tampilan tabel dan setel tinggi baris ke dimensi otomatis dengan dalam pemuatan viewdid:

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

Untuk memperbaiki masalah pemuatan awal tersebut, terapkan metode layoutIfNeeded dengan dalam sel tampilan tabel kustom:

class CustomTableViewCell: UITableViewCell {

override func awakeFromNib() {
    super.awakeFromNib()
    self.layoutIfNeeded()
    // Initialization code
}
}
bikram sapkota
sumber
Penting untuk menyetel estimatedRowHeightke nilai> 0 dan bukan UITableViewAutomaticDimension(yaitu -1), jika tidak, tinggi baris otomatis tidak akan berfungsi.
PJ_Finnegan
5

Saya telah mencoba sebagian besar jawaban atas pertanyaan ini dan tidak bisa mendapatkan satu pun dari mereka untuk bekerja. Satu-satunya solusi fungsional yang saya temukan adalah menambahkan yang berikut ini ke UITableViewControllersubkelas saya :

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

The UIView.performWithoutAnimationpanggilan diperlukan, jika tidak, anda akan melihat normal tampilan tabel animasi sebagai beban controller tampilan.

Chris Vig
sumber
bekerja dan pasti lebih baik daripada menjalankan reloadData dua kali
Jimmy George Thomas
2
Sihir hitam. Melakukannya di dalam viewWillAppeartidak berhasil untuk saya, tetapi melakukannya di dalam viewDidAppearberhasil.
Martin
Solusi ini 'berhasil' untuk saya saat diterapkan di viewDidAppear. Ini sama sekali tidak optimal untuk pengalaman pengguna karena konten tabel melompat-lompat saat ukuran yang benar terjadi.
richardpiazza
luar biasa saya telah mencoba banyak contoh tetapi gagal Anda adalah setan
Shakeel Ahmed
Sama seperti @martin
AJ Hernandez
3

menelepon ke cell.layoutIfNeeded()dalam cellForRowAtberfungsi untuk saya di iOS 10 dan iOS 11, tetapi tidak di iOS 9.

untuk mendapatkan pekerjaan ini di ios 9 juga, saya menelepon cell.layoutSubviews()dan berhasil.

mnemonic23
sumber
2

Melampirkan tangkapan layar untuk referensi AndaBagi saya tidak ada pendekatan ini yang berhasil, tetapi saya menemukan bahwa label tersebut memiliki Preferred Widthset eksplisit di Interface Builder. Menghapus itu (menghapus centang "Eksplisit") dan kemudian menggunakan UITableViewAutomaticDimensionberfungsi seperti yang diharapkan.

Jack James
sumber
2

Saya mencoba semua solusi di halaman ini tetapi menghapus centang kelas ukuran penggunaan kemudian memeriksanya lagi memecahkan masalah saya.

Sunting: Menghapus centang kelas ukuran menyebabkan banyak masalah di papan cerita jadi saya mencoba solusi lain. Saya mengisi tampilan tabel saya di pengontrol tampilan viewDidLoaddan viewWillAppearmetode saya. Ini memecahkan masalah saya.

ACengiz
sumber
1

Saya memiliki masalah dengan mengubah ukuran label, jadi saya hanya perlu melakukan
chatTextLabel.text = chatMessage.message chatTextLabel? .UpdateConstraints () setelah menyiapkan teks

// kode lengkap

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)

  }
Svitlana
sumber
1

Dalam kasus saya, saya memperbarui di siklus lain. Jadi tinggi tableViewCell diperbarui setelah labelText disetel. Saya menghapus blok asinkron.

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
     // }
}
Den Jo
sumber
Saya juga memiliki blok asinkron tetapi diperlukan? Apakah tidak ada alternatif lain?
Nazar Medeiros
1

Pastikan Anda tidak menyetel teks label dalam metode delegasi 'willdisplaycell' pada tampilan tabel. Setel teks label dalam metode delegasi 'cellForRowAtindexPath' untuk penghitungan ketinggian dinamis.

Sama-sama :)

Pranav Rivankar
sumber
1

Dalam kasus saya, tampilan tumpukan di dalam sel menyebabkan masalah. Sepertinya itu bug. Setelah saya menghapusnya, masalahnya teratasi.

Guilherme Carvalho
sumber
1
Saya mencoba semua solusi yang lain, hanya solusi Anda bekerja untuk terima kasih telah membagikannya
tamtoum1987
1

Masalahnya adalah bahwa sel awal dimuat sebelum kita memiliki tinggi baris yang valid. Solusinya adalah memaksa tabel memuat ulang saat tampilan muncul.

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}
alicanozkara.dll
sumber
ini adalah solusi yang jauh lebih baik yang membantu saya. Itu tidak mengoreksi 100% sel tetapi hingga 80% mengambil ukuran sebenarnya. Terima kasih banyak
TheTravloper
1

Hanya untuk iOS 12+, 2019 dan seterusnya ...

Contoh berkelanjutan dari ketidakmampuan Apple yang aneh, di mana masalah terus berlanjut selama bertahun-tahun.

Tampaknya memang demikian

        cell.layoutIfNeeded()
        return cell

akan memperbaikinya. (Anda kehilangan beberapa kinerja tentu saja.)

Begitulah hidup dengan Apple.

Gendut
sumber
0

Dalam kasus saya, masalah ketinggian sel terjadi setelah tampilan tabel awal dimuat, dan tindakan pengguna terjadi (mengetuk tombol di sel yang memiliki efek mengubah tinggi sel). Saya tidak dapat membuat sel mengubah tingginya kecuali saya melakukannya:

[self.tableView reloadData];

Saya sudah mencoba

[cell layoutIfNeeded];

tapi itu tidak berhasil.

Chris Prince
sumber
0

Di Swift 3. Saya harus memanggil self.layoutIfNeeded () setiap kali saya memperbarui teks sel yang dapat digunakan kembali.

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

Tak satu pun dari solusi di atas berhasil tetapi kombinasi saran berikut berhasil.

Harus menambahkan yang berikut ini di viewDidLoad ().

DispatchQueue.main.async {

        self.tableView.reloadData()

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

        self.tableView.reloadData()

    }

Kombinasi reloadData, setNeedsLayout, dan layoutIfNeeded di atas berfungsi, tetapi tidak yang lain. Bisa jadi khusus untuk sel dalam proyek sekalipun. Dan ya, harus memanggil reloadData dua kali untuk membuatnya berfungsi.

Setel juga berikut ini di viewDidLoad

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight

Di tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath)

cell.setNeedsLayout()
cell.layoutIfNeeded() 
Jimmy George Thomas
sumber
Kemiripan saya reloadData/beginUpdates/endUpdates/reloadDatajuga bekerja; reloadData harus dipanggil untuk kedua kalinya. Tidak perlu membungkusnya async.
ray
0

Saya mengalami masalah ini dan memperbaikinya dengan memindahkan kode inisialisasi tampilan / label saya DARI tableView(willDisplay cell:)KE tableView(cellForRowAt:).

mengangkat dan mengkilap
sumber
yang BUKAN cara yang disarankan untuk menginisialisasi sel. willDisplayakan memiliki kinerja yang lebih baik daripada cellForRowAt. Gunakan yang terbaru hanya untuk membuat sel yang tepat.
Martin
2 tahun kemudian, saya membaca komentar lama yang saya tulis itu. Ini adalah nasihat yang buruk. Meskipun willDisplaymemiliki performa yang lebih baik, iS merekomendasikan untuk menginisialisasi UI sel Anda cellForRowAtsaat tata letaknya otomatis. Memang, tata letak dihitung oleh UIKit setelah cellForRowAt et sebelum willDisplay. Jadi jika tinggi sel Anda bergantung pada isinya, inisialisasi konten label (atau apa pun) di cellForRowAt.
Martin
-1
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{


//  call the method dynamiclabelHeightForText
}

gunakan metode di atas yang mengembalikan ketinggian baris secara dinamis. Dan tetapkan tinggi dinamis yang sama ke label yang Anda gunakan.

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


}

Kode ini membantu Anda menemukan ketinggian dinamis untuk teks yang ditampilkan di label.

Ramesh Muthe
sumber
Sebenarnya Ramesh, ini tidak diperlukan selama properti tableView.estimatedRowHeight dan tableView.rowHeight = UITableViewAutomaticDimension disetel. Selain itu, pastikan sel khusus memiliki batasan yang sesuai pada berbagai widget dan contentView.
BonanzaDriver