UITableView dynamiczne wysokości komórek poprawiają się tylko po przewinięciu

Mam UITableView z niestandardowym UITableViewCell zdefiniowanym w storyboardzie za pomocą układu automatycznego. Komórka posiada kilka multilinii UILabels.

UITableView wydaje się prawidłowo obliczać wysokość komórek, ale dla pierwszych kilku komórek wysokość ta nie jest odpowiednio podzielona między etykiety. Po trochę przewinięciu wszystko działa zgodnie z oczekiwaniami (nawet komórki, które początkowo były nieprawidłowe).

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

Czy jest coś, czego może mi brakować, co powoduje, że auto layout nie jest w stanie wykonać swojej pracy pierwsze kilka razy?

Stworzyłem przykładowy projekt , który demonstruje to zachowanie.

Przykładowy projekt: Widok górnej części tabeli z przykładowego projektu przy pierwszym załadowaniu.Przykładowy projekt: te same komórki po przewinięciu w dół i kopii zapasowej.

Author: Adobels, 2014-09-22

22 answers

Nie wiem, czy jest to wyraźnie udokumentowane, czy nie, ale dodanie [cell layoutIfNeeded] przed zwróceniem komórki rozwiązuje twój problem.

- (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;
}
 105
Author: rintaro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-22 09:58:36

Dodawanie [cell layoutIfNeeded] w cellForRowAtIndexPath nie działa w przypadku komórek, które są początkowo przewijane poza widok.

Nie ma też przedruku z [cell setNeedsLayout].

Nadal musisz przewijać niektóre komórki na zewnątrz i z powrotem do widoku, aby poprawnie zmienić ich rozmiar.

Jest to dość frustrujące, ponieważ większość deweloperów ma dynamiczny Typ, AutoLayout i Samorozmiarowe komórki działające poprawnie-z wyjątkiem tego irytującego przypadku. Ten błąd wpływa na wszystkie moje" wyższe " Kontrolery widoku tabeli.

 20
Author: Scenario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-25 13:20:03

To działało dla mnie, gdy inne podobne rozwiązania nie:

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

Wydaje się to prawdziwym błędem, ponieważ jestem bardzo zaznajomiony z AutoLayout i jak używać UITableViewAutomaticDimension, jednak nadal czasami natykam się na ten problem. Cieszę się, że w końcu znalazłem coś, co działa jako obejście.

 20
Author: Michael Peterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-27 09:15:16

Miałem takie samo doświadczenie w jednym z moich projektów.

Dlaczego tak się dzieje?

Komórka zaprojektowana w Storyboardzie z pewną szerokością dla jakiegoś urządzenia. Na przykład 400px. Na przykład etykieta ma taką samą szerokość. Gdy ładuje się z storyboard, ma szerokość 400px.

Oto problem:

tableView:heightForRowAtIndexPath: wywołane przed układem komórki To podwidywacze.

Obliczono więc wysokość dla etykiety i komórki o szerokości 400px. Ale uruchamiasz na urządzeniu z Ekranem, na przykład 320px. I to wysokość obliczana automatycznie jest nieprawidłowa. Tylko dlatego, że cell ' s layoutSubviews dzieje się dopiero po tableView:heightForRowAtIndexPath: Nawet jeśli ręcznie ustawisz preferredMaxLayoutWidth dla swojej etykiety w layoutSubviews, to nie pomaga.

Moje rozwiązanie:

1) podklasa UITableView i nadpisanie dequeueReusableCellWithIdentifier:forIndexPath:. Ustaw szerokość komórki równą szerokości tabeli i Wymuś jej układ.

- (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) podklasa UITableViewCell. Ustaw ręcznie preferredMaxLayoutWidth dla etykiet w layoutSubviews. Trzeba również ręcznie layout contentView, ponieważ nie układa się automatycznie po zmianie ramki komórki (I Nie wiem dlaczego, ale jest)

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.contentView layoutIfNeeded];
    self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
 11
Author: Vitaliy Gozhenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-28 21:54:47

Mam podobny problem, przy pierwszym ładowaniu wysokość wiersza nie została obliczona, ale po pewnym przewinięciu lub przejściu do innego ekranu i wracam do tego ekranu wiersze są obliczane. Przy pierwszym ładowaniu moje pozycje są ładowane z Internetu, a przy drugim ładowaniu moje pozycje są ładowane najpierw z podstawowych danych i przeładowywane z Internetu i zauważyłem, że wysokość wierszy jest obliczana przy przeładowaniu z Internetu. Więc zauważyłem, że kiedy tableView.reloadData() jest wywoływana podczas animacji segue (ta sama problem z push I present segue), wysokość wiersza nie została obliczona. Więc ukryłem tableview w widoku inicjalizacji i umieścić activity loader, aby zapobiec brzydki efekt dla użytkownika i wywołuję tableView.przeładuj dane po 300ms i teraz problem jest rozwiązany. Myślę, że to błąd UIKit, ale to obejście robi sztuczkę.

I umieścić te linie (Swift 3.0) w moim item Load completion handler

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

To wyjaśnia, dlaczego dla niektórych osób, umieścić reloadData w layoutSubviews rozwiązać wydanie

 7
Author: alvinmeimoun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-26 21:11:00

W moim przypadku ostatnia linia UILabel została obcięta, gdy komórka była wyświetlana po raz pierwszy. Stało się to dość losowo, a jedynym sposobem na poprawny rozmiar było przewinięcie komórki z widoku i przywrócenie jej. Próbowałem wszystkich możliwych rozwiązań wyświetlanych do tej pory (layoutIfNeeded..reloadData) ale u mnie nic nie działało. Sztuczka polegała na ustawieniu "Autoshrink" na Minimuum Font Scale (dla mnie 0.5). Spróbuj

 4
Author: Claus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-01 13:36:38

Dodaj ograniczenie dla całej zawartości w niestandardowej komórce widoku tabeli, a następnie oszacuj wysokość wiersza widoku tabeli i ustaw wysokość wiersza na automatyczny wymiar z ładowaniem widoku:

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

Aby naprawić początkowy problem z ładowaniem, zastosuj metodę layoutIfNeeded z w niestandardowej komórce widoku tabeli:

class CustomTableViewCell: UITableViewCell {

override func awakeFromNib() {
    super.awakeFromNib()
    self.layoutIfNeeded()
    // Initialization code
}
}
 4
Author: bikram sapkota,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-08 16:21:33

Próbowałem większości odpowiedzi na to pytanie i nie mogłem zmusić żadnej z nich do pracy. Jedynym funkcjonalnym rozwiązaniem, jakie znalazłem, było dodanie do mojej UITableViewController podklasy:

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

Wywołanie UIView.performWithoutAnimation jest wymagane, w przeciwnym razie zobaczysz normalną animację widoku tabeli podczas ładowania kontrolera widoku.

 3
Author: Chris Vig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-30 19:44:01

Ustawienie preferredMaxLayoutWidth pomaga w moim przypadku. Dodałem

cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width
W moim kodzie.

Zobacz także tekst w jednej linii zawiera dwie linie w UILabel i http://openradar.appspot.com/17799811 .

 2
Author: Xhacker Liu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:31

Próbowałem wszystkich rozwiązań na tej stronie, ale odznaczenie użyj klas rozmiaru, a następnie sprawdzenie go ponownie rozwiązało mój problem.

Edit: odznaczanie klas wielkości powoduje wiele problemów na storyboardzie, więc próbowałem innego rozwiązania. Wypełniłem tableView w metodzie kontrolera widoku ViewDidLoad i ViewWillAppear. To rozwiązało mój problem.

 2
Author: ACengiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-25 16:26:11

Dołączanie zrzutu ekranu dla Twojego odniesieniadla mnie żadne z tych podejść nie zadziałało, ale odkryłem, że etykieta ma wyraźny Preferred Width ustawiony w Kreatorze interfejsów. Usunięcie tego (odznaczenie "Explicit") i użycie UITableViewAutomaticDimension działało zgodnie z oczekiwaniami.

 2
Author: Jack James,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-03 16:22:11

Mam problem ze zmianą rozmiaru etykiety więc muszę to zrobić
chatTextLabel.text = "chatMessage",wiadomość chatTextLabel?.updateConstraints () po ustawieniu tekstu

// Pełny 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
Author: Svitlana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-20 13:54:41
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{


//  call the method dynamiclabelHeightForText
}

Użyj powyższej metody, która dynamicznie Zwraca wysokość wiersza. I przypisać tę samą dynamiczną wysokość do używanej etykiety.

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


}

Ten kod pomaga znaleźć dynamiczną wysokość wyświetlania tekstu na etykiecie.

 0
Author: Ramesh Muthe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-25 13:20:30

W moim przypadku problem z wysokością komórki ma miejsce po załadowaniu początkowego widoku tabeli i wykonaniu czynności użytkownika(dotknięcie przycisku w komórce, który ma wpływ na zmianę wysokości komórki). Nie udało mi się zmusić komórki do zmiany wysokości chyba, że to zrobię:

[self.tableView reloadData];

I did try

[cell layoutIfNeeded];
Ale to nie zadziałało.
 0
Author: Chris Prince,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-04 20:23:05

W Swift 3. Musiałem zadzwonić do siebie.layoutIfNeeded() za każdym razem aktualizuję tekst komórki wielokrotnego użytku.

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
Author: Naloiko Eugene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-13 06:51:59

W moim przypadku aktualizowałem w innym cyklu. Tak więc wysokość tableViewCell została zaktualizowana po ustawieniu labelText. Usunąłem blok asynchroniczny.

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
     // }
}
 0
Author: Den Jo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-04 05:52:14

Żadne z powyższych rozwiązań nie zadziałało, ale następująca kombinacja sugestii nie zadziałała.

Musiałem dodać następujące elementy w viewDidLoad ().

DispatchQueue.main.async {

        self.tableView.reloadData()

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

        self.tableView.reloadData()

    }

Powyższa kombinacja reloadData, setNeedsLayout i layoutIfNeeded zadziałała, ale żadna inna. Może to być specyficzne dla komórek w projekcie. I tak, musiałem dwukrotnie wywołać reloadData, aby to działało.

Ustaw również następujące opcje w viewDidLoad

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight

In tableView (_tableView: UITableView, cellForRowAt indexPath: IndexPath)

cell.setNeedsLayout()
cell.layoutIfNeeded() 
 0
Author: Jimmy George Thomas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-22 15:17:55

Upewnij się tylko, że nie ustawiasz tekstu etykiety w metodzie' willdisplaycell ' w widoku tabeli. Ustaw tekst etykiety w metodzie' cellForRowAtindexPath ' do dynamicznego obliczania wysokości.

Zapraszam:)

 0
Author: Pranav Rivankar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-25 06:12:15

W moim przypadku problem powodował widok stosu w komórce. Najwyraźniej to robak. Gdy go usunąłem, problem został rozwiązany.

 0
Author: Guilherme Carvalho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-26 10:53:11

Napotkałem ten problem i naprawiłem go, przenosząc mój kod inicjalizacji widoku/etykiety z tableView(willDisplay cell:) na tableView(cellForRowAt:).

 0
Author: rpm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-12 03:18:06

Problem polega na tym, że początkowe komórki ładują się przed uzyskaniem prawidłowej wysokości wiersza. Obejście polega na wymuszeniu przeładowania tabeli, gdy pojawi się widok.

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}
 0
Author: alicanozkara,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-20 13:44:49

Na ios 10 i ios 11 zadziałało, ale nie na ios 9.

Aby uzyskać tę pracę również na ios 9, dzwonię cell.layoutSubviews() i udało się.

 0
Author: mnemonic23,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-28 17:02:23