UITableView dinamik hücre yükseklikleri yalnızca biraz kaydırmadan sonra düzelir


117

Bir var UITableViewbir özel ile UITableViewCelloto düzeni kullanarak, film şeridindeki tanımladı. Hücrenin birkaç çoklu hattı vardır UILabels.

UITableViewDüzgün hesapla hücre zirvelere, ancak yükseklik düzgün etiketler arasında bölünmüş olmadığını ilk birkaç hücreler için görünür. Biraz kaydırdıktan sonra, her şey beklendiği gibi çalışır (başlangıçta hatalı olan hücreler bile).

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

Eksik olabileceğim bir şey var mı, bu da otomatik düzenin ilk birkaç kez işini yapamamasına neden oluyor mu?

Bu davranışı gösteren örnek bir proje oluşturdum .

Örnek proje: İlk yüklemede örnek projeden tablo görünümü. Örnek proje: Aşağı ve yukarı kaydırdıktan sonra aynı hücreler.


1
Ben de bu sorunla karşı karşıyayım, Ama aşağıdaki cevap benim için çalışmıyor, Bu konuda herhangi bir yardım
Shangari C

1
Aynı sorunla karşı karşıya, layoutIfNeeded for cell bende çalışmıyor mu? Başka öneri
Max

1
Harika yer. Bu, 2019'da hala bir sorun: /
Fattie

Aşağıdaki bağlantıda bana neyin yardımcı olduğunu buldum - bu, değişken yükseklik tablosu görünüm hücrelerinin oldukça kapsamlı bir tartışması: stackoverflow.com/a/18746930/826946
Andy Weinstein

Yanıtlar:


139

Bunun açıkça belgelenip belgelenmediğini bilmiyorum, ancak [cell layoutIfNeeded]hücreyi döndürmeden önce eklemek sorununuzu çözer.

- (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
Yine de merak ediyorum, neden sadece ilk seferde gerekli? Aramayı -layoutIfNeededhücrenin içine koyarsam da işe yarıyor -awakeFromNib. Sadece layoutIfNeededneden gerekli olduğunu bildiğim yerleri aramayı tercih ederim .
blackp

2
Benim için çalıştı! Ve bunun neden gerekli olduğunu bilmek isterim!
Mustafa

Herhangi bir X herhangi birinden farklı bir boyut sınıfını oluşturursanız işe yaramadı
Andrei Konstantinov

@AndreyKonstantinov Evet, beden sınıfıyla çalışmıyor, beden sınıfıyla nasıl çalışmasını sağlayabilirim?
z22

Boyut sınıfı olmadan çalışır, beden sınıfı ile herhangi bir çözüm var mı?
Yuvrajsinh

38

Bu, diğer benzer çözümler olmadığında benim için çalıştı:

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

AutoLayout'a ve UITableViewAutomaticDimension'ın nasıl kullanılacağına çok aşina olduğum için bu gerçek bir hata gibi görünüyor, ancak yine de bazen bu sorunla karşılaşıyorum. Sonunda geçici çözüm olarak çalışan bir şey bulduğuma sevindim.


1
Kabul edilen yanıttan daha iyidir çünkü yalnızca hücre, her hücre görüntüsü yerine xib'den yüklenirken çağrılır. Çok daha az kod satırı da.
Robert Wagstaff

3
Aramayı unutmasuper.didMoveToSuperview()
cicerocamargo

@cicerocamargo Apple hakkında şunları söylüyor didMoveToSuperview: "Bu yöntemin varsayılan uygulaması hiçbir şey yapmaz."
Ivan Smetanin

4
@IvanSmetanin Varsayılan uygulamanın bir şey yapmaması önemli değil, gelecekte gerçekten bir şeyler yapabileceği için yine de süper yöntemi çağırmalısınız.
TNguyen

(Sadece bu konuda KESİNLİKLE süper çağırmalısınız. Ekibin genel olarak birden fazla hücreyi alt sınıflara ayırmadığı bir proje görmedim, bu yüzden elbette hepsini aldığınızdan emin olmalısınız. VE TNguyen'in söylediği ayrı bir konu.)
Fattie

22

Ekleme [cell layoutIfNeeded]içinde cellForRowAtIndexPathbaşlangıçta dışı görünümü kaydırılan kısmındaki hücreler için çalışma yapmaz.

Önsöz de yapmaz [cell setNeedsLayout].

Yine de doğru şekilde yeniden boyutlandırmaları için belirli hücreleri dışarı ve tekrar görünüme kaydırmanız gerekir.

Çoğu geliştiricinin Dinamik Tip, Otomatik Yerleşim ve Kendini Boyutlandıran Hücrelerin düzgün çalışmasından dolayı bu oldukça sinir bozucu - bu can sıkıcı durum dışında. Bu hata, tüm "uzun" tablo görünümü denetleyicilerimi etkiliyor.


Benim için aynı. İlk kaydırdığımda hücrenin boyutu 44px. Hücreyi gözden uzak tutmak ve geri dönmek için kaydırırsam, düzgün boyutlandırılır. Herhangi bir çözüm buldunuz mu?
Maksimum

Teşekkürler @ ashy_32bit bu benim için de çözdü.
ImpurestClub

basit bir hata gibi görünüyor, @Scenario değil mi? korkunç şeyler.
Şişko

3
[cell layoutSubviews]LayoutIfNeeded yerine kullanmak olası bir düzeltme olabilir. Bakınız stackoverflow.com/a/33515872/1474113
ypresto

14

Projelerimden birinde aynı deneyimi yaşadım.

Neden olur?

Storyboard'da bazı cihazlar için belirli bir genişlikte tasarlanmış hücre. Örneğin 400px. Örneğin etiketinizin genişliği aynı. Film şeridinden yüklendiğinde genişliği 400 pikseldir.

İşte bir sorun:

tableView:heightForRowAtIndexPath: Hücre düzeninden önce çağrılır, alt görünümlerdir.

Böylece etiket ve hücre genişliği 400 piksel olarak hesaplandı. Ancak ekranlı cihazda, örneğin 320px'de çalıştırıyorsunuz. Ve otomatik olarak hesaplanan bu yükseklik yanlıştır. Sırf hücre layoutSubviewssadece sonra olur Çünkü etiketinizi manuel olarak tableView:heightForRowAtIndexPath: ayarlasanız bile yardımcı olmaz.preferredMaxLayoutWidthlayoutSubviews

Çözümüm:

1) Alt sınıf UITableViewve geçersiz kılma dequeueReusableCellWithIdentifier:forIndexPath:. Hücre genişliğini tablo genişliğine eşit olarak ayarlayın ve hücrenin düzenini zorlayın.

- (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) Alt sınıf UITableViewCell. İçindeki preferredMaxLayoutWidthetiketleriniz için manuel olarak ayarlayın layoutSubviews. Ayrıca el ile düzene ihtiyacınız var contentView, çünkü hücre çerçevesi değişikliğinden sonra otomatik olarak yerleşmiyor (nedenini bilmiyorum ama öyle)

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

1
Bu sorunla karşılaştığımda, tableview çerçevesinin doğru olduğundan da emin olmam gerekiyordu, bu yüzden tableview doldurulmadan önce [self.tableview layoutIfNeeded] 'i çağırdım (viewDidLoad'da).
GK100

Yanlış bir genişlikle ilgisi olduğunu düşünmekten asla vazgeçmedim. cell.frame.size.width = tableview.frame.widthSonra cell.layoutIfNeeded()içinde cellForRowAtişlevin benim için hile yaptı
Cam Connor

10

Benzer bir problemim var, ilk yüklemede satır yüksekliği hesaplanmadı ancak biraz kaydırma veya başka bir ekrana geçtikten sonra bu ekrana geri döndüğümde satırlar hesaplanıyor. İlk yüklemede eşyalarım internetten yüklenir ve ikinci yüklemede eşyalarım önce Core Data'dan yüklenir ve internetten yeniden yüklenir ve satır yüksekliğinin internetten yeniden yüklemede hesaplandığını fark ettim. Bu yüzden, segment animasyonu sırasında tableView.reloadData () çağrıldığında (push ve mevcut segment ile aynı sorun), satır yüksekliğinin hesaplanmadığını fark ettim. Bu yüzden tablo görünümünü görünüm başlangıcında gizledim ve kullanıcıya çirkin bir etkiyi önlemek için bir aktivite yükleyici koydum ve 300ms sonra tableView.reloadData'yı çağırdım ve şimdi sorun çözüldü. Bunun bir UIKit hatası olduğunu düşünüyorum ama bu geçici çözüm hile yapıyor.

Bu satırları (Swift 3.0) öğe yükleme işleyicime koydum

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

Bu, bazı insanlar için layoutSubviews alt görünümlerine bir reloadData koymanın sorunu çözmesinin nedenini açıklar


Bu benim için de çalışan tek WA. Haricinde isHidden kullanmıyorum, ancak veri kaynağını eklerken tablonun alfa değerini 0'a, ardından yeniden yükledikten sonra 1'e ayarlayın.
PJ_Finnegan

6

Yukarıdaki çözümlerden hiçbiri benim için işe yaramadı, işe yarayan bu sihir tarifi: onları şu sırayla arayın:

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

tableView verilerim bir web servisinden dolduruluyor, bağlantının geri çağrılmasında yukarıdaki satırları yazıyorum.


1
Hızlı 5 için bu, koleksiyon görünümünde bile işe yaradı, tanrı seni korusun kardeşim
Govani Dhruv Vijaykumar

Benim için bu, hücrenin doğru yüksekliğini ve düzenini döndürdü, ancak içinde veri yoktu
Darrow Hartman,

Bu satırlardan sonra setNeedsDisplay () deneyin
JAHelia

5

Benim durumumda, UILabel'in son satırı, hücre ilk kez görüntülendiğinde kesildi. Oldukça rastgele oldu ve doğru boyutlandırmanın tek yolu, hücreyi görünümün dışına kaydırıp geri getirmekti. Şimdiye kadar görüntülenen tüm olası çözümleri denedim (layoutIfNeeded..reloadData) ama benim için hiçbir şey işe yaramadı. İşin püf noktası "Autoshrink" i Minimuum Yazı Tipi Ölçeğine (benim için 0.5) ayarlamaktı. Bir şans ver


Bu, yukarıda listelenen diğer çözümlerden hiçbirini uygulamadan benim için çalıştı. Harika bul.
mckeejm

2
Ancak bu, metni otomatik olarak küçültür ... Bir tablo görünümünde neden farklı boyutlarda metne sahip olmak istersiniz? Berbat görünüyor.
lucius degeer

5

Bir tablo görünümü özel hücresindeki tüm içerik için bir sınırlama ekleyin, ardından tablo görünümü satır yüksekliğini tahmin edin ve bir viewdid yüklemesinde satır yüksekliğini otomatik boyuta ayarlayın:

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

Bu ilk yükleme sorununu düzeltmek için özel bir tablo görünümü hücresinde layoutIfNeeded yöntemini uygulayın:

class CustomTableViewCell: UITableViewCell {

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

estimatedRowHeightDeğer> 0'a ayarlamak ve UITableViewAutomaticDimension(-1 olan) değere ayarlamak önemlidir , aksi takdirde otomatik satır yüksekliği çalışmayacaktır.
PJ_Finnegan

5

Bu sorunun yanıtlarının çoğunu denedim ve hiçbirini işe yaramadı. Bulduğum tek işlevsel çözüm, UITableViewControlleralt sınıfıma aşağıdakileri eklemekti :

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

UIView.performWithoutAnimationÇağrı aksi takdirde görünümü denetleyicisi yükler gibi normal tablo görünümü animasyon göreceksiniz, gereklidir.


İki kez reloadData çağırma daha iyi kesinlikle işe ve
Jimmy George Thomas

2
Kara büyü. Bunu yapmak viewWillAppearbenim için işe yaramadı, ama yapmak işe viewDidAppearyaradı.
Martin

Bu çözüm, viewDidAppear'da uygulandığında benim için işe yaradı. Doğru boyutlandırma gerçekleştiğinde tablo içeriği sıçradığından, kullanıcı deneyimi için hiçbir şekilde optimal değildir.
richardpiazza

inanılmaz bir sürü örnek denedim ama başarısız oluyorsun sen iblisin
Shakeel Ahmed

@Martin ile aynı
AJ Hernandez


3

çağıran cell.layoutIfNeeded()içeridecellForRowAt değil IOS 9, IOS 10 ve IOS 11 benim için çalıştı.

ios 9'da da bu işi almak için aradım cell.layoutSubviews()ve hile yaptı.


2

Referansınız için ekran görüntüsü eklemekBenim için bu yaklaşımlardan hiçbiri işe yaramadı, ancak etiketin Preferred WidthInterface Builder'da açık bir kümesi olduğunu keşfettim . Bunu kaldırmak ("Açık" seçeneğinin işaretini kaldırmak) ve ardından kullanılması UITableViewAutomaticDimensionbeklendiği gibi çalıştı.


2

Bu sayfadaki tüm çözümleri denedim, ancak kullanım boyut sınıflarının işaretini kaldırıp tekrar kontrol etmek sorunumu çözdü.

Düzenleme: Boyut sınıflarının işaretinin kaldırılması, film şeridinde birçok soruna neden oluyor, bu yüzden başka bir çözüm denedim. Tablo görünümümü, görünüm denetleyicimde viewDidLoadve viewWillAppearyöntemlerinde doldurdum . Bu benim sorunumu çözdü.


1

Etiketi yeniden boyutlandırmakla ilgili bir sorunum var, bu yüzden sadece yapmam gerekiyor
metni ayarladıktan sonra chatTextLabel.text = chatMessage.message chatTextLabel? .UpdateConstraints () istiyorum

// tam kod

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

Benim durumumda, diğer döngüde güncelleme yapıyordum. Böylece, labelText ayarlandıktan sonra tableViewCell yüksekliği güncellendi. Eşzamansız bloğu sildim.

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
     // }
}

Benim de zaman uyumsuz bir bloğum var ama gerekli mi? Alternatif yok mu?
Nazar Medeiros

1

Tablo görünümünün 'willdisplaycell' delege yönteminde etiket metnini ayarlamadığınızdan emin olun. Dinamik yükseklik hesaplaması için 'cellForRowAtindexPath' temsilci yönteminde etiket metnini ayarlayın.

Rica ederim :)


1

Benim durumumda, hücredeki yığın görünümü soruna neden oluyordu. Görünüşe göre bu bir hata. Onu çıkardıktan sonra sorun çözüldü.


1
Diğer tüm çözümleri denedim, yalnızca çözümünüz işe yaradı, paylaştığınız için teşekkür ederim
tamtoum1987

1

Sorun, geçerli bir satır yüksekliğine sahip olmadan önce ilk hücrelerin yüklenmesidir. Çözüm, görünüm göründüğünde tablonun yeniden yüklenmesini zorlamaktır.

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

Bu bana yardımcı olan çok çok daha iyi bir çözümdü. Hücrenin% 100'ünü düzeltmedi, ancak% 80'e kadarı gerçek boyutlarını alıyordu. Çok teşekkürler
TheTravloper

1

Yalnızca iOS 12+ için, 2019 sonrası ...

Sorunların kelimenin tam anlamıyla yıllarca sürdüğü, Apple'ın ara sıra ortaya çıkan tuhaf yetersizliğinin devam eden bir örneği.

Öyle görünüyor ki

        cell.layoutIfNeeded()
        return cell

düzeltecek. (Elbette biraz performans kaybediyorsunuz.)

Apple ile hayat böyledir.


0

Benim durumumda, hücre yüksekliğiyle ilgili sorun, ilk tablo görünümü yüklendikten ve bir kullanıcı eylemi gerçekleştikten sonra gerçekleşir (hücre yüksekliğini değiştirme etkisi olan bir hücredeki bir düğmeye dokunmak). Bunu yapmadığım sürece hücrenin yüksekliğini değiştirmesini sağlayamadım:

[self.tableView reloadData];

Denedim

[cell layoutIfNeeded];

ama bu işe yaramadı.


0

Swift 3'te, yeniden kullanılabilir hücrenin metnini her güncellediğimde self.layoutIfNeeded () 'i çağırmak zorunda kaldım.

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

Yukarıdaki çözümlerden hiçbiri işe yaramadı, ancak aşağıdaki önerilerin kombinasyonu işe yaramadı.

ViewDidLoad () 'a aşağıdakileri eklemek gerekiyordu.

DispatchQueue.main.async {

        self.tableView.reloadData()

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

        self.tableView.reloadData()

    }

Yukarıdaki reloadData, setNeedsLayout ve layoutIfNeeded kombinasyonu işe yaradı, ancak diğerleri işe yaramadı. Yine de projedeki hücrelere özel olabilir. Ve evet, çalışması için reloadData'yı iki kez çağırmak zorunda kaldı.

Ayrıca viewDidLoad'da aşağıdakileri ayarlayın

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight

TableView içinde (_ tableView: UITableView, cellForRowAt indexPath: IndexPath)

cell.setNeedsLayout()
cell.layoutIfNeeded() 

Benim benzerim reloadData/beginUpdates/endUpdates/reloadDatade çalışıyor; reloadData gereken ikinci kez çağrılabilir. Sarmanıza gerek yok async.
ray

0

Bu sorunla karşılaştım ve görünüm / etiket başlatma kodumu FROM tableView(willDisplay cell:)TO'ya taşıyarak düzelttim tableView(cellForRowAt:).


bu, bir hücreyi başlatmak için önerilen yol DEĞİLDİR . willDisplaydaha iyi bir performansa sahip olacak cellForRowAt. En sonuncuyu yalnızca doğru hücreyi anlamak için kullanın.
Martin

2 yıl sonra yazdığım o eski yorumu okuyorum. Bu kötü bir tavsiyeydi. Daha willDisplayiyi performansa sahip olsanız bile cellForRowAt, düzen otomatik olduğunda hücre kullanıcı arayüzünüzü başlatmanız önerilir . Aslında, düzen UIKit tarafından cellForRowAt et sonra willDisplay'den önce hesaplanır. Dolayısıyla, hücre yüksekliğiniz içeriğine bağlıysa, etiket içeriğini (veya her neyse) içinde başlatın cellForRowAt.
Martin

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


//  call the method dynamiclabelHeightForText
}

satırın yüksekliğini dinamik olarak döndüren yukarıdaki yöntemi kullanın. Ve kullandığınız etikete aynı dinamik yüksekliği atayın.

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


}

Bu kod, etikette görüntülenen metnin dinamik yüksekliğini bulmanıza yardımcı olur.


Aslında Ramesh, tableView.estimatedRowHeight ve tableView.rowHeight = UITableViewAutomaticDimension özellikleri ayarlandığı sürece buna ihtiyaç duyulmamalıdır. Ayrıca, özel hücrenin çeşitli pencere öğeleri ve contentView üzerinde uygun kısıtlamalara sahip olduğundan emin olun.
BonanzaDriver
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.