Korzystanie z automatycznego układu w UITableView do dynamicznych układów komórek i zmiennych wysokości wierszy

1501

W jaki sposób korzystasz z automatycznego układu w obrębie UITableViewCells w widoku tabeli, aby zawartość i widoki podrzędne każdej komórki określały wysokość wiersza (sama / automatycznie), zachowując płynne przewijanie?

Smileyborg
źródło
Oto przykładowy kod w swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur,
Aby zrozumieć większość odpowiedzi dotyczących posiadania wielowierszowego UILabel, musisz w pełni zrozumieć, w jaki sposób contentSizei jak preferredMaxLayoutWidthpracować. Zobacz tutaj . Biorąc to pod uwagę, jeśli poprawnie skonfigurujesz swoje ograniczenia, nie powinieneś ich potrzebować, preferredMaxLayoutWidthaw rzeczywistości może stworzyć nieoczekiwane wyniki.
Honey,

Odpowiedzi:

2386

TL; DR: Nie lubisz czytać? Przejdź bezpośrednio do przykładowych projektów na GitHub:

Opis koncepcyjny

Pierwsze 2 kroki poniżej mają zastosowanie niezależnie od tego, dla których wersji iOS opracowujesz.

1. Skonfiguruj i dodaj ograniczenia

W swojej UITableViewCellpodklasy, dodać ograniczenia tak, że subviews ogniwa mają swoje krawędzie przypięte do krawędzi komórki contentView (najważniejsze na górnej i dolnej krawędzi). UWAGA: nie przypinaj widoków podrzędnych do samej komórki; tylko do komórki contentView! Pozwól, aby rzeczywisty rozmiar zawartości tych widoków podrzędnych sterował wysokością widoku zawartości komórki widoku tabeli, upewniając się, że odporność na kompresję treści i ograniczenia przytulania treści w wymiarze pionowym dla każdego widoku podrzędnego nie są zastępowane przez dodane ograniczenia o wyższym priorytecie. ( Huh? Kliknij tutaj. )

Pamiętaj, że pomysł polega na tym, aby widoki podrzędne komórki były połączone pionowo z widokiem zawartości komórki, aby mogły one „wywierać presję” i sprawić, że widok zawartości rozszerzy się, aby je dopasować. Korzystając z przykładowej komórki z kilkoma podstronami, oto wizualna ilustracja tego, jak powinny wyglądać niektóre (nie wszystkie!) Ograniczenia:

Przykładowa ilustracja ograniczeń w komórce widoku tabeli.

Można sobie wyobrazić, że w miarę dodawania większej ilości tekstu do wieloliniowej etykiety ciała w powyższej przykładowej komórce, będzie on musiał rosnąć pionowo, aby dopasować się do tekstu, co skutecznie wymusi wzrost komórki. (Oczywiście, musisz poprawnie dopasować ograniczenia, aby działało to poprawnie!)

Poprawienie ograniczeń jest zdecydowanie najtrudniejszą i najważniejszą częścią uzyskania dynamicznych wysokości komórek podczas pracy z Auto Layout. Jeśli popełnisz tutaj błąd, może to uniemożliwić działanie całej reszty - nie spiesz się! Zalecam skonfigurowanie ograniczeń w kodzie, ponieważ dokładnie wiesz, które ograniczenia są dodawane gdzieś, i łatwiej jest debugować, gdy coś pójdzie nie tak. Dodanie ograniczeń w kodzie może być tak samo łatwe i znacznie potężniejsze niż narzędzie do tworzenia interfejsów za pomocą kotwic układu lub jednego z fantastycznych interfejsów API typu open source dostępnych w GitHub.

  • Jeśli dodajesz ograniczenia w kodzie, powinieneś to zrobić raz z poziomu updateConstraintsmetody podklasy UITableViewCell. Należy pamiętać, że updateConstraintsmożna go wywołać więcej niż jeden raz, więc aby uniknąć dodawania tych samych ograniczeń więcej niż jeden raz, należy zawinąć kod dodający ograniczenia updateConstraintsw celu sprawdzenia właściwości boolowskiej, takiej jak didSetupConstraints(która została ustawiona na TAK po uruchomieniu ograniczenia -dodanie kodu raz). Z drugiej strony, jeśli masz kod, który aktualizuje istniejące ograniczenia (np. Dostosowując constantwłaściwość w przypadku niektórych ograniczeń), umieść to w miejscu, updateConstraintsale poza sprawdzaniem, didSetupConstraintsaby można go było uruchomić za każdym razem, gdy wywoływana jest metoda.

2. Określ unikalne identyfikatory ponownego wykorzystania komórki w widoku tabeli

Dla każdego unikalnego zestawu ograniczeń w komórce użyj unikalnego identyfikatora ponownego użycia komórki. Innymi słowy, jeśli twoje komórki mają więcej niż jeden unikalny układ, każdy unikalny układ powinien otrzymać swój własny identyfikator ponownego wykorzystania. (Dobra wskazówka, że ​​musisz użyć nowego identyfikatora ponownego wykorzystania, to gdy Twój wariant komórki ma inną liczbę widoków podrzędnych lub widoki podrzędne są ułożone w inny sposób).

Na przykład, jeśli wyświetlasz wiadomość e-mail w każdej komórce, możesz mieć 4 unikalne układy: wiadomości z tylko tematem, wiadomości z tematem i treścią, wiadomości z tematem i załącznikiem do zdjęcia oraz wiadomości z tematem, ciało i załącznik do zdjęcia. Każdy układ ma zupełnie inne ograniczenia wymagane do jego osiągnięcia, więc po zainicjowaniu komórki i dodaniu ograniczeń dla jednego z tych typów komórek, komórka powinna otrzymać unikalny identyfikator ponownego wykorzystania specyficzny dla tego typu komórki. Oznacza to, że po usunięciu kolejki z komórki do ponownego użycia ograniczenia zostały już dodane i są gotowe do użycia dla tego typu komórek.

Zauważ, że z powodu różnic w wewnętrznej wielkości zawartości komórki z tymi samymi ograniczeniami (typami) mogą nadal mieć różne wysokości! Nie należy mylić zasadniczo różnych układów (różnych ograniczeń) z różnymi obliczonymi ramkami widoku (rozwiązanymi z identycznych ograniczeń) z powodu różnych rozmiarów treści.

  • Nie dodawaj komórek z zupełnie innymi zestawami ograniczeń do tej samej puli ponownego użycia (tj. Używaj tego samego identyfikatora ponownego wykorzystania), a następnie nie próbuj usuwać starych ograniczeń i konfigurować nowe ograniczenia od zera po każdym usunięciu kolejki. Wewnętrzny silnik Auto Layout nie jest zaprojektowany do obsługi zmian na dużą skalę w ograniczeniach i zobaczysz ogromne problemy z wydajnością.

W systemie iOS 8 - komórki samopoziomujące

3. Włącz szacowanie wysokości wiersza

Aby włączyć komórki rozmiaru widoku tabeli, należy ustawić właściwość rowHeight widoku tabeli na UITableViewAutomaticDimension. Musisz także przypisać wartość do właściwości oszacowanejRowHeight. Po ustawieniu obu tych właściwości system korzysta z automatycznego układu do obliczania rzeczywistej wysokości wiersza

Apple: Praca z komórkami samosprawiającymi widok tabeli

W systemie iOS 8 Apple zinternalizowało większość pracy, którą wcześniej musiałeś wykonać przed iOS 8. Aby zezwolić na działanie mechanizmu komórki samopoziomującej, musisz najpierw ustawić rowHeightwłaściwość w widoku tabeli na stałą UITableViewAutomaticDimension. Następnie wystarczy włączyć oszacowanie wysokości wiersza, ustawiając właściwość widoku tabeli na estimatedRowHeightniezerową wartość, na przykład:

self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is

Zapewnia to widokowi tabeli tymczasowe oszacowanie / symbol zastępczy dla wysokości wierszy komórek, które nie są jeszcze wyświetlane na ekranie. Następnie, gdy komórki te będą przewijać się na ekranie, zostanie obliczona rzeczywista wysokość wiersza. Aby określić rzeczywistą wysokość dla każdego wiersza, widok tabeli automatycznie pyta każdą komórkę, jaka wysokość contentViewmusi być oparta na znanej stałej szerokości widoku treści (która jest oparta na szerokości widoku tabeli, pomniejszonej o wszelkie dodatkowe rzeczy, takie jak indeks sekcji lub widok akcesoriów) oraz ograniczenia automatycznego układu dodane do widoku zawartości i widoków podrzędnych komórki. Po określeniu tej rzeczywistej wysokości komórki stara szacowana wysokość wiersza jest aktualizowana o nową rzeczywistą wysokość (i wszelkie korekty zawartości contentSize / contentOffset widoku tabeli są dokonywane według potrzeb).

Ogólnie rzecz biorąc, podane oszacowanie nie musi być bardzo dokładne - służy jedynie do prawidłowego rozmiaru wskaźnika przewijania w widoku tabeli, a widok tabeli dobrze dostosowuje wskaźnik przewijania w celu uzyskania niepoprawnych oszacowań przewiń komórki na ekranie. Powinieneś ustawić estimatedRowHeightwłaściwość w widoku tabeli (w viewDidLoadlub podobnym) na stałą wartość, która jest „średnią” wysokością wiersza. Tylko jeśli wysokość rzędów jest skrajnie zmienna (np. Różni się o rząd wielkości) i zauważysz, że wskaźnik przewijania „przeskakuje” podczas przewijania, należy zadać sobie trud wykonania tableView:estimatedHeightForRowAtIndexPath:minimalnego obliczenia wymaganego w celu uzyskania dokładniejszego oszacowania dla każdego rzędu.

Obsługa systemu iOS 7 (samodzielne wdrażanie automatycznego określania rozmiaru komórki)

3. Wykonaj Layout Pass i uzyskaj wysokość komórki

Najpierw utwórz instancję poza ekranem komórki widoku tabeli, po jednej instancji dla każdego identyfikatora ponownego wykorzystania , która jest używana wyłącznie do obliczeń wysokości. (Poza ekranem oznacza, że ​​odwołanie do komórki jest przechowywane we właściwości / ivar na kontrolerze widoku i nigdy nie wraca z tableView:cellForRowAtIndexPath:widoku tabeli do rzeczywistego renderowania na ekranie.) Następnie komórkę należy skonfigurować z dokładną zawartością (np. Tekst, obrazy itp.) trzymałby, gdyby miał być wyświetlany w widoku tabeli.

Następnie zmuś komórkę do natychmiastowego ułożenia swoich widoków podrzędnych, a następnie użyj systemLayoutSizeFittingSize:metody na UITableViewCell, contentViewaby dowiedzieć się, jaka jest wymagana wysokość komórki. Użyj, UILayoutFittingCompressedSizeaby uzyskać najmniejszy rozmiar wymagany do dopasowania całej zawartości komórki. Wysokość może być następnie zwrócona z tableView:heightForRowAtIndexPath:metody delegowania.

4. Użyj szacunkowych wysokości rzędów

Jeśli widok tabeli zawiera więcej niż kilkadziesiąt wierszy, przekonasz się, że wykonanie funkcji automatycznego wiązania układu może szybko ugrzęznąć w głównym wątku podczas pierwszego ładowania widoku tabeli, tak jak tableView:heightForRowAtIndexPath:jest to wywoływane w każdym wierszu przy pierwszym załadowaniu ( w celu obliczenia rozmiaru wskaźnika przewijania).

Począwszy od systemu iOS 7, możesz (i absolutnie należy) korzystać z estimatedRowHeightwłaściwości w widoku tabeli. Zapewnia to widokowi tabeli tymczasowe oszacowanie / symbol zastępczy dla wysokości wierszy komórek, które nie są jeszcze wyświetlane na ekranie. Następnie, gdy komórki te będą przewijać się na ekranie, rzeczywista wysokość wiersza zostanie obliczona (przez wywołanie tableView:heightForRowAtIndexPath:), a szacowana wysokość zaktualizowana o rzeczywistą.

Ogólnie rzecz biorąc, podane oszacowanie nie musi być bardzo dokładne - służy jedynie do prawidłowego rozmiaru wskaźnika przewijania w widoku tabeli, a widok tabeli dobrze dostosowuje wskaźnik przewijania w celu uzyskania niepoprawnych oszacowań przewiń komórki na ekranie. Powinieneś ustawić estimatedRowHeightwłaściwość w widoku tabeli (w viewDidLoadlub podobnym) na stałą wartość, która jest „średnią” wysokością wiersza. Tylko jeśli wysokość rzędów jest skrajnie zmienna (np. Różni się o rząd wielkości) i zauważysz, że wskaźnik przewijania „przeskakuje” podczas przewijania, należy zadać sobie trud wykonania tableView:estimatedHeightForRowAtIndexPath:minimalnego obliczenia wymaganego w celu uzyskania dokładniejszego oszacowania dla każdego rzędu.

5. (W razie potrzeby) Dodaj buforowanie wysokości wiersza

Jeśli wykonałeś wszystkie powyższe czynności i nadal odkrywasz, że wydajność jest niedopuszczalnie niska podczas rozwiązywania ograniczeń tableView:heightForRowAtIndexPath:, niestety będziesz musiał zaimplementować buforowanie dla wysokości komórek. (Takie podejście sugerują inżynierowie Apple.) Ogólny pomysł polega na tym, że silnik Autolayout rozwiązuje ograniczenia po raz pierwszy, a następnie buforuje obliczoną wysokość dla tej komórki i używa wartości buforowanej dla wszystkich przyszłych żądań dotyczących wysokości tej komórki. Sztuką jest oczywiście wyczyszczenie pamięci podręcznej wysokości komórki, gdy zdarzy się coś, co może spowodować zmianę wysokości komórki - przede wszystkim, gdy zmieni się zawartość tej komórki lub gdy wystąpią inne ważne zdarzenia (takie jak dostosowanie użytkownika suwak rozmiaru tekstu typu dynamicznego).

Ogólny przykładowy kod dla iOS 7 (z dużą ilością soczystych komentarzy)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Determine which reuse identifier should be used for the cell at this 
    // index path, depending on the particular layout required (you may have
    // just one, or may have many).
    NSString *reuseIdentifier = ...;

    // Dequeue a cell for the reuse identifier.
    // Note that this method will init and return a new cell if there isn't
    // one available in the reuse pool, so either way after this line of 
    // code you will have a cell with the correct constraints ready to go.
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];

    // Configure the cell with content for the given indexPath, for example:
    // cell.textLabel.text = someTextForThisCell;
    // ...

    // Make sure the constraints have been set up for this cell, since it 
    // may have just been created from scratch. Use the following lines, 
    // assuming you are setting up constraints from within the cell's 
    // updateConstraints method:
    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    // If you are using multi-line UILabels, don't forget that the 
    // preferredMaxLayoutWidth needs to be set correctly. Do it at this 
    // point if you are NOT doing it within the UITableViewCell subclass 
    // -[layoutSubviews] method. For example: 
    // cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Determine which reuse identifier should be used for the cell at this 
    // index path.
    NSString *reuseIdentifier = ...;

    // Use a dictionary of offscreen cells to get a cell for the reuse 
    // identifier, creating a cell and storing it in the dictionary if one 
    // hasn't already been added for the reuse identifier. WARNING: Don't 
    // call the table view's dequeueReusableCellWithIdentifier: method here 
    // because this will result in a memory leak as the cell is created but 
    // never returned from the tableView:cellForRowAtIndexPath: method!
    UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
    if (!cell) {
        cell = [[YourTableViewCellClass alloc] init];
        [self.offscreenCells setObject:cell forKey:reuseIdentifier];
    }

    // Configure the cell with content for the given indexPath, for example:
    // cell.textLabel.text = someTextForThisCell;
    // ...

    // Make sure the constraints have been set up for this cell, since it 
    // may have just been created from scratch. Use the following lines, 
    // assuming you are setting up constraints from within the cell's 
    // updateConstraints method:
    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    // Set the width of the cell to match the width of the table view. This
    // is important so that we'll get the correct cell height for different
    // table view widths if the cell's height depends on its width (due to 
    // multi-line UILabels word wrapping, etc). We don't need to do this 
    // above in -[tableView:cellForRowAtIndexPath] because it happens 
    // automatically when the cell is used in the table view. Also note, 
    // the final width of the cell may not be the width of the table view in
    // some cases, for example when a section index is displayed along 
    // the right side of the table view. You must account for the reduced 
    // cell width.
    cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));

    // Do the layout pass on the cell, which will calculate the frames for 
    // all the views based on the constraints. (Note that you must set the 
    // preferredMaxLayoutWidth on multiline UILabels inside the 
    // -[layoutSubviews] method of the UITableViewCell subclass, or do it 
    // manually at this point before the below 2 lines!)
    [cell setNeedsLayout];
    [cell layoutIfNeeded];

    // Get the actual height required for the cell's contentView
    CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    // Add an extra point to the height to account for the cell separator, 
    // which is added between the bottom of the cell's contentView and the 
    // bottom of the table view cell.
    height += 1.0;

    return height;
}

// NOTE: Set the table view's estimatedRowHeight property instead of 
// implementing the below method, UNLESS you have extreme variability in 
// your row heights and you notice the scroll indicator "jumping" 
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Do the minimal calculations required to be able to return an 
    // estimated row height that's within an order of magnitude of the 
    // actual height. For example:
    if ([self isTallCellAtIndexPath:indexPath]) {
        return 350.0;
    } else {
        return 40.0;
    }
}

Przykładowe projekty

Te projekty są w pełni działającymi przykładami widoków tabeli ze zmiennymi wysokościami wierszy ze względu na komórki widoku tabeli zawierające dynamiczną zawartość w UILabels.

Xamarin (C # /. NET)

Jeśli korzystasz z Xamarin, sprawdź ten przykładowy projekt opracowany przez @KentBoogaart .

smileyborg
źródło
1
Chociaż działa to dobrze, uważam, że nieco szacuje wymagane rozmiary (prawdopodobnie z powodu różnych problemów z zaokrąglaniem) i muszę dodać kilka punktów do ostatecznej wysokości, aby cały mój tekst zmieścił się w etykietach
DBD
5
@ Alex311 Bardzo interesujące, dziękuję za podanie tego przykładu. Zrobiłem małe testy na moim końcu i napisałem kilka komentarzy tutaj: github.com/Alex311/TableCellWithAutoLayout/commit/…
smileyborg
3
Poleciłbym MOCNIE buforowanie komórki dla każdego typu identyfikatora ponownego użycia komórki. Za każdym razem, gdy usuwasz wysokość, wyjmujesz komórkę z kolejki, która nie jest ponownie dodawana. Buforowanie może znacznie zmniejszyć liczbę wywołań inicjalizatora komórek tabeli. Z jakiegoś powodu, gdy nie buforowałem, ilość używanej pamięci ciągle rosła, gdy przewijałem.
Alex311,
1
Właściwie scenorysy. Nie sądzę, że to działa na prototypowe komórki (przynajmniej bez ponownego tworzenia całej VC). Może być możliwe całkowite uniknięcie wycieku przez usunięcie z kolejki w heightForRowAtIndexPath, zatrzymanie komórki i zwrócenie jej przy następnym cellForRowAtIndexPathwywołaniu.
nschum
2
Czy iOS8wdrożenie jest nadal zalecane iOS9i iOS10czy istnieją nowe podejścia od czasu opublikowania tej odpowiedzi?
koen
175

W przypadku iOS 8 powyżej jest to naprawdę proste:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableView.automaticDimension
}

lub

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

Ale w iOS 7 kluczem jest obliczyć wysokość po autoukładzie:

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height
}

Ważny

  • Jeśli wiele wierszy etykiet, nie zapomnij ustawić numberOfLinessię 0.

  • Nie zapomnij label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

Pełny przykładowy kod znajduje się tutaj .

William Hu
źródło
7
Myślę, że nie musimy implementować funkcji heightForRowAtIndexPath w przypadku OS8
jpulikkottil
Jak zauważa @eddwinpaz na innej odpowiedzi, ważne jest, aby ograniczenia stosowane do blokowania wysokości wiersza NIE zawierały marginesu.
Richard
1
+1 dla „Jeśli etykiety wielu linii, nie zapomnij ustawić numberOfLines na 0” to spowodowało, że moje komórki nie były dynamiczne.
Ian-Fogelman
Nazywaj mnie maniakiem kontroli, ale nawet w czasach iOS 11 nadal nie lubię używać UITableViewAutomaticDimension. Miałem z tym złe doświadczenia w przeszłości. Jako takie zazwyczaj używam wymienionych tutaj rozwiązań iOS7. Notatka Williama, aby nie zapomnieć label.preferredMaxLayoutWidth uratowała mnie tutaj.
Brian Sachetta
Kiedy przewijamy, etykieta zaczyna pojawiać się w wielu liniach, a wysokość wiersza również nie rośnie
Karanveer Singh
93

Szybki przykład zmiennej wysokości UITableViewCell

Zaktualizowano dla Swift 3

Szybka odpowiedź Williama Hu jest dobra, ale pomaga mi zrobić kilka prostych, ale szczegółowych kroków podczas nauki robienia czegoś po raz pierwszy. Poniższy przykład to mój projekt testowy podczas nauki tworzenia UITableViewzmiennej wysokości komórek. Oparłem go na tym prostym przykładzie UITableView dla Swift .

Gotowy projekt powinien wyglądać następująco:

wprowadź opis zdjęcia tutaj

Utwórz nowy projekt

Może to być tylko aplikacja z pojedynczym widokiem.

Dodaj kod

Dodaj nowy plik Swift do swojego projektu. Nazwij to MyCustomCell. Ta klasa będzie przechowywać wyloty widoków, które dodajesz do komórki w serii ujęć. W tym podstawowym przykładzie będziemy mieli tylko jedną etykietę w każdej komórce.

import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UILabel!
}

Podłączymy to gniazdko później.

Otwórz ViewController.swift i upewnij się, że masz następującą treść:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

Ważna uwaga:

  • Poniższe dwa wiersze kodu (wraz z automatycznym układem) umożliwiają zmienną wysokość komórki:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension

Skonfiguruj scenorys

Dodaj widok tabeli do kontrolera widoku i użyj automatycznego układu, aby przypiąć go do czterech stron. Następnie przeciągnij komórkę widoku tabeli do widoku tabeli. I do komórki Prototyp przeciągnij etykietę. Użyj automatycznego układu, aby przypiąć etykietę do czterech krawędzi widoku zawartości komórki widoku tabeli.

wprowadź opis zdjęcia tutaj

Ważna uwaga:

  • Automatyczny układ współpracuje z dwoma ważnymi wierszami kodu, o których wspomniałem powyżej. Jeśli nie użyjesz automatycznego układu, to nie zadziała.

Inne ustawienia IB

Niestandardowa nazwa klasy i identyfikator

Wybierz komórkę widoku tabeli i ustaw niestandardową klasę na MyCustomCell(nazwa klasy w dodanym pliku Swift). Ustaw także Identyfikator na cell(ten sam ciąg, którego użyliśmy cellReuseIdentifierw powyższym kodzie.

wprowadź opis zdjęcia tutaj

Zero linii dla etykiety

Ustaw liczbę linii 0w etykiecie. Oznacza to wiele linii i pozwala na zmianę rozmiaru etykiety w zależności od jej zawartości.

wprowadź opis zdjęcia tutaj

Podłącz haki

  • Przeciągnij kontrolkę z widoku tabeli w serii ujęć do tableViewzmiennej w ViewControllerkodzie.
  • Zrób to samo dla Etykiety w komórce Prototypu dla myCellLabelzmiennej w MyCustomCellklasie.

Skończone

Powinieneś być teraz w stanie uruchomić swój projekt i uzyskać komórki o zmiennej wysokości.

Notatki

  • Ten przykład działa tylko na iOS 8 i późniejszych. Jeśli nadal potrzebujesz obsługiwać iOS 7, to nie zadziała.
  • Własne niestandardowe komórki w przyszłych projektach będą prawdopodobnie miały więcej niż jedną etykietę. Upewnij się, że wszystko jest dobrze przypięte, aby automatyczny układ mógł określić prawidłową wysokość do użycia. Być może będziesz musiał użyć pionowego oporu kompresji i przytulania. Więcej informacji na ten temat znajduje się w tym artykule .
  • Jeśli nie przypinasz krawędzi początkowej i końcowej (lewej i prawej), konieczne może być również ustawienie etykiety, preferredMaxLayoutWidthaby wiedziała, kiedy zawinąć linię. Na przykład, jeśli dodałeś ograniczenie Środek w poziomie do etykiety w powyższym projekcie zamiast przypinać krawędzie początkowe i końcowe, musisz dodać tę linię do tableView:cellForRowAtIndexPathmetody:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Zobacz też

Suragch
źródło
Czy to nie lepiej ustawić preferredMaxLayoutWidthprzeciw komórce contentSize? W ten sposób, jeśli masz widok akcesorium lub został on przesunięty do edycji, to nadal będzie brany pod uwagę?
Honey,
@ Kochanie, możesz mieć rację. Nie korzystam z iOS od około roku i jestem zbyt zardzewiały, aby teraz dobrze Ci odpowiadać.
Suragch,
65

Zawarłem rozwiązanie iOS7 @ smileyborg w jednej kategorii

Postanowiłem zawinąć to sprytne rozwiązanie @smileyborg w UICollectionViewCell+AutoLayoutDynamicHeightCalculationkategorię.

Kategoria rozwiązuje również problemy przedstawione w odpowiedzi @ wildmonkey (ładowanie komórki ze stalówki i systemLayoutSizeFittingSize:zwracanie CGRectZero)

Nie uwzględnia buforowania, ale teraz odpowiada moim potrzebom. Możesz go kopiować, wklejać i hakować.

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
 */
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

/**
 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *
 *  @param name Name of the nib file.
 *
 *  @return collection view cell for using to calculate content based height
 */
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

/**
 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *
 *  @param block Render the model data to your UI elements in this block
 *
 *  @return Calculated constraint derived height
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

/**
 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;

@end

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

Przykład użycia:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

Na szczęście nie będziemy musieli robić tego jazzu na iOS8, ale jest na razie!

Adam Waite
źródło
2
Powinieneś być w stanie po prostu użyć: [YourCell new]i użyć tego jako manekina. Tak długo, jak długo tworzony jest kod budujący kod ograniczenia, a programowo uruchamiasz przejście do układu, powinieneś być gotowy.
Adam Waite
1
Dzięki! To działa. Twoja kategoria jest świetna. To uświadomiło mi, że ta technika również działa UICollectionViews.
Ricardo Sanchez-Saez
2
Jak byś to zrobił za pomocą prototypowej komórki zdefiniowanej w serii ujęć?
David Potter
57

Oto moje rozwiązanie:

Trzeba powiedzieć zanim załaduje się widokiem. W przeciwnym razie nie będzie mógł zachowywać się zgodnie z oczekiwaniami.TableViewestimatedHeight

Cel C

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}

Zaktualizuj do Swift 4.2

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}
Eddwin Paz
źródło
2
Prawidłowe skonfigurowanie autoukładu wraz z dodawaniem tego kodu w viewDidLoad załatwiło sprawę.
Bruce,
1
ale co jeśli estimatedRowHeightzmienia się rząd po rzędzie? powinienem przekraczać lub niedoszacowywać? użyć minimalnej lub maksymalnej wysokości, w której używam tableView?
János
1
@ János jest to punkt rowHeight. Aby to zrobić, zachowaj się zgodnie z oczekiwaniami, musisz użyć ograniczeń bez marginesów i wyrównać obiekty z TableViewCell. i zakładam, że używasz UITextView, więc nadal musisz usunąć autoscroll = false, w przeciwnym razie utrzyma wysokość i wysokość względna nie będzie działać zgodnie z oczekiwaniami.
Eddwin Paz
1
To zdecydowanie najbardziej niezawodne rozwiązanie. Nie martw się estimatedRowHeight, to głównie wpływa na rozmiar paska przewijania, nigdy na wysokość rzeczywistych komórek. Bądź odważny na wybranej wysokości: wpłynie to na animację wstawiania / usuwania.
SwiftArchitect
Oto przykładowy kod w swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur,
47

Rozwiązanie zaproponowane przez @smileyborg jest prawie idealne. Jeśli masz komórkę niestandardową i chcesz mieć jedną lub więcej UILabelz dynamicznymi wysokościami, wówczas metoda systemLayoutSizeFittingSize w połączeniu z włączoną funkcją AutoLayout zwraca a, CGSizeZerochyba że przeniesiesz wszystkie ograniczenia komórki z komórki do jej contentView (zgodnie z sugestią @TomSwift tutaj Jak zmienić rozmiar superview do dopasować wszystkie widoki podrzędne z funkcją autolayout? ).

Aby to zrobić, musisz wstawić następujący kod do niestandardowej implementacji UITableViewCell (dzięki @Adrian).

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

Mieszanie odpowiedzi @smileyborg z tym powinno działać.

wildmonkey
źródło
systemLayoutSizeFittingSizenależy wywołać w contentView, a nie w komórce
onmyway133
25

To wystarczająco ważna sprawa, na którą natknąłem się, aby opublikować jako odpowiedź.

Odpowiedź @ smileyborg jest w większości poprawna. Jeśli jednak masz kod w layoutSubviewsmetodzie niestandardowej klasy komórki, na przykład ustawiając preferredMaxLayoutWidth, to nie będzie on uruchamiany z tym kodem:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

Przez chwilę mnie to zaskoczyło. Potem zdałem sobie sprawę, że to dlatego, że uruchamiają tylko podgląd układu contentViewna komórce, a nie na samej komórce.

Mój działający kod wygląda następująco:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

Pamiętaj, że jeśli tworzysz nową komórkę, jestem pewien, że nie musisz dzwonić setNeedsLayouttak, jak powinna być już ustawiona. W przypadkach, w których zapisujesz odwołanie do komórki, prawdopodobnie powinieneś to nazwać. Tak czy inaczej, nie powinno to nic zranić.

Kolejna wskazówka, jeśli używasz podklas komórek, w których ustawiasz takie rzeczy preferredMaxLayoutWidth. Jak wspomniała @smileyborg, „komórka widoku tabeli nie ma jeszcze ustalonej szerokości do szerokości widoku tabeli”. To prawda i kłopoty, jeśli wykonujesz pracę w swojej podklasie, a nie w kontrolerze widoku. Możesz jednak po prostu ustawić ramkę komórki w tym punkcie, używając szerokości tabeli:

Na przykład w obliczeniach wysokości:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(Zdarzyło mi się buforować tę konkretną komórkę do ponownego użycia, ale to nie ma znaczenia).

Bob Spryn
źródło
19

Tak długo, jak układ w komórce jest dobry.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

Aktualizacja: należy użyć dynamicznej zmiany rozmiaru wprowadzonej w iOS 8.

Chris Van Buskirk
źródło
To działa na mnie na iOS7, to jest teraz OK, aby zadzwonić tableView:cellForRowAtIndexPath:na tableView:heightForRowAtIndexPath:teraz?
Ants
Ok, więc to nie działa, ale gdy zgłoszę systemLayoutSizeFittingSize:się tableView:cellForRowAtIndexPath:i buforować wynik wtedy, a następnie użyć, że tableView:heightForRowAtIndexPath:to działa dobrze tak długo, jak ograniczenia są ustawione prawidłowo oczywiście!
Ants
Działa to tylko wtedy, gdy użyjesz dequeueReusableCellWithIdentifier: zamiast dequeueReusableCellWithIdentifier: forIndexPath:
Antoine
1
Naprawdę nie sądzę, aby wywoływanie tableView: cellForRowAtIndexPath: bezpośrednio jest dobrym sposobem.
Itachi,
19

(dla Xcode 8.x / Xcode 9.x czytaj na dole)

Uważaj na następujący problem w Xcode 7.x, który może być źródłem zamieszania:

Konstruktor interfejsów nie obsługuje poprawnie konfiguracji automatycznego doboru komórek. Nawet jeśli twoje ograniczenia są absolutnie ważne, IB nadal będzie narzekać i da ci mylące sugestie i błędy. Powodem jest to, że IB nie chce zmieniać wysokości wiersza zgodnie z Twoimi ograniczeniami (aby komórka pasowała do Twojej zawartości). Zamiast tego utrzymuje stałą wysokość wiersza i zaczyna sugerować zmianę wiązań, które należy zignorować .

Na przykład wyobraź sobie, że wszystko skonfigurowałeś dobrze, bez ostrzeżeń, bez błędów, wszystko działa.

wprowadź opis zdjęcia tutaj

Teraz, jeśli zmienisz rozmiar czcionki (w tym przykładzie zmieniam rozmiar czcionki etykiety opisu z 17.0 na 18.0).

wprowadź opis zdjęcia tutaj

Ponieważ rozmiar czcionki został zwiększony, etykieta chce teraz zajmować 3 wiersze (wcześniej zajmowała 2 wiersze).

Jeśli Konstruktor interfejsów działał zgodnie z oczekiwaniami, zmieniłby wysokość komórki, aby uwzględnić nową etykietę. Jednak tak naprawdę dzieje się tak, że IB wyświetla czerwoną ikonę błędu automatycznego układu i sugeruje zmianę priorytetów przytulania / kompresji.

wprowadź opis zdjęcia tutaj

Powinieneś zignorować te ostrzeżenia. Zamiast tego możesz * ręcznie zmienić wysokość wiersza w (wybierz Komórka> Inspektor rozmiarów> Wysokość wiersza).

wprowadź opis zdjęcia tutaj

Zmieniłem tę wysokość jedno kliknięcie na raz (używając steppera góra / dół), aż znikną błędy czerwonej strzałki! (faktycznie dostaniesz żółte ostrzeżenia, w tym momencie po prostu idź i zrób „aktualizację ramek”, wszystko powinno działać).

* Pamiętaj, że tak naprawdę nie musisz usuwać tych czerwonych błędów lub żółtych ostrzeżeń w Konstruktorze interfejsów - w czasie wykonywania wszystko będzie działało poprawnie (nawet jeśli IB pokazuje błędy / ostrzeżenia). Upewnij się tylko, że w czasie wykonywania w dzienniku konsoli nie pojawią się żadne błędy AutoLayout.

W rzeczywistości próba zawsze aktualizowania wysokości wiersza w IB jest bardzo irytująca, a czasem prawie niemożliwa (z powodu wartości ułamkowych).

Aby zapobiec irytującym ostrzeżeniom / błędom IB, możesz wybrać związane z tym widoki i wybrać Size InspectorwłaściwośćAmbiguityVerify Position Only

wprowadź opis zdjęcia tutaj


Xcode 8.x / Xcode 9.x wydaje się (czasami) robić rzeczy inaczej niż Xcode 7.x, ale nadal nieprawidłowo. Na przykład, nawet jeśli compression resistance priority/ hugging prioritysą ustawione na wymagane (1000), Konstruktor interfejsów może rozciągnąć lub przyciąć etykietę, aby dopasować ją do komórki (zamiast zmieniać rozmiar wysokości komórki, aby dopasować ją do etykiety). W takim przypadku może nawet nie wyświetlać żadnych ostrzeżeń lub błędów AutoLayout. Lub czasami robi dokładnie to, co zrobił Xcode 7.x, opisane powyżej.

Nikolay Suvandzhiev
źródło
cześć, czy można podać dynamiczną wysokość komórki, która ma widok tabeli z komórką z dynamiczną zawartością komórki?
Jignesh B,
18

Aby ustawić automatyczny wymiar dla wysokości wiersza i szacowanej wysokości wiersza, wykonaj następujące kroki, aby automatyczny wymiar był skuteczny dla układu wysokości komórki / wiersza.

  • Przypisywanie i wdrażanie danych widoku tabeli Źródło i delegowanie
  • Przypisz UITableViewAutomaticDimensiondo rowHeight & oszacowanyRowHeight
  • Wdrażanie metod delegate / dataSource (tj. heightForRowAtI zwracanie wartości UITableViewAutomaticDimensiondo niego)

-

Cel C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

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

    return UITableViewAutomaticDimension;
}

Szybki:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

Dla instancji etykiety w UITableviewCell

  • Ustaw liczbę linii = 0 (tryb przerwania linii = obetnij ogon)
  • Ustaw wszystkie ograniczenia (górny, dolny, prawy lewy) w odniesieniu do jego kontenera podglądu / komórki.
  • Opcjonalne : Ustaw minimalną wysokość etykiety, jeśli chcesz, aby etykieta obejmowała minimalny obszar pionowy, nawet jeśli nie ma danych.

wprowadź opis zdjęcia tutaj

Uwaga : Jeśli masz więcej niż jedną etykietę (UIElements) z dynamiczną długością, którą należy dostosować do rozmiaru zawartości: Dostosuj „Priorytet przytulania zawartości i priorytetu odporności na ściskanie” dla etykiet, które chcesz rozwinąć / skompresować z wyższym priorytetem.

Krunal
źródło
1
Dziękuję wydaje się, że jest to jasne i proste rozwiązanie, ale mam problem, że nie używam etykiet, ale widoków tekstowych, więc potrzebuję wzrostu wysokości wiersza w miarę dodawania danych. Moim problemem jest więc przekazanie informacji do heightForRowAt. Mogę zmierzyć wysokość moich zmieniających się widoków tekstu, ale teraz muszę zmienić wysokość wiersza. Byłbym wdzięczny za pomoc
Jeremy Andrews,
@JeremyAndrews Sure pomoże ci. Podnieś swoje pytanie za pomocą kodu źródłowego, który wypróbowałeś, i opisz szczegółowo problem.
Krunal
Działa dla mnie bez wdrażania tableView: heightForRowźródła danych.
Hemang
@Hemang - Od iOS 11+ działa bez niego tableView: heightForRow. (dla iOS 10- tableView: heightForRowjest wymagany)
Krunal
1
@Hemang - Rozwiązanie zależy od dokładnej definicji zapytania. Oto ogólne wspólne rozwiązanie dla twojego zapytania. Umieść warunek w środku tableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
Krunal
16

Podobnie jak @ Bob-Spryn natknąłem się na wystarczająco ważną gotcha, że ​​publikuję to jako odpowiedź.

Walczyłem z użytkownika @ smileyborg odpowiedź na chwilę. Gotcha, na którą natrafiłem, to jeśli zdefiniowałeś swoją prototypową komórkę w IB z dodatkowymi elementami ( UILabels, UIButtonsitp.) W IB, gdy tworzysz komórkę za pomocą [ [YourTableViewCellClass alloc] init]nie utworzy instancji wszystkich innych elementów w tej komórce, chyba że masz napisany kod, aby to zrobić. (Miałem podobne doświadczenia z initWithStyle.)

Aby scenariusz tworzył wszystkie dodatkowe elementy, uzyskaj komórkę za pomocą [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](Nie, [tableView dequeueReusableCellWithIdentifier:forIndexPath:]ponieważ spowoduje to interesujące problemy.) Gdy to zrobisz, wszystkie elementy zdefiniowane w IB zostaną utworzone.

nickb
źródło
15

Dynamiczny widok tabeli Wysokość komórki i automatyczny układ

Dobry sposób na rozwiązanie problemu z automatycznym układem scenorysu:

- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
  static RWImageCell *sizingCell = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
  });

  [sizingCell setNeedsLayout];
  [sizingCell layoutIfNeeded];

  CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
  return size.height;
}
Mohnasm
źródło
1
Zostało to już szeroko uwzględnione w zaakceptowanej odpowiedzi na to pytanie.
smileyborg,
2
Tak, wiem ... ale nie chciałem używać PureLayout, a „sztuczka” dispatch_once bardzo mi pomogła, pracując tylko przy użyciu Storyboard.
Mohnasm,
13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

wprowadź opis zdjęcia tutaj

Abo3atef
źródło
13

Kolejne „rozwiązanie”: pomiń całą tę frustrację i zamiast tego użyj UIScrollView, aby uzyskać wynik, który wygląda identycznie jak UITableView.

To było dla mnie bolesne „rozwiązanie” po tym, jak spędziłem dosłownie ponad 20 bardzo frustrujących godzin, próbując zbudować coś takiego, co sugerował smileyborg, i nie udawało mu się przez wiele miesięcy oraz trzy wersje wydań App Store.

Uważam, że jeśli naprawdę potrzebujesz wsparcia dla systemu iOS 7 (dla nas jest to niezbędne), technologia jest po prostu zbyt krucha i spróbujesz wyciągnąć włosy. I że UITableView jest generalnie całkowitą nadwyżką, chyba że korzystasz z niektórych zaawansowanych funkcji edycji wierszy i / lub naprawdę potrzebujesz obsługiwać ponad 1000 „wierszy” (w naszej aplikacji jest to realistycznie nigdy więcej niż 20 wierszy).

Dodatkową zaletą jest to, że kod staje się niesamowicie prosty w porównaniu do wszystkich bzdur delegowanych i tam iz powrotem, które są dostarczane z UITableView. To tylko jedna pętla kodu w viewOnLoad, która wygląda elegancko i jest łatwa w zarządzaniu.

Oto kilka wskazówek, jak to zrobić:

  1. Używając Storyboard lub pliku stalówki, utwórz ViewController i powiązany widok główny.

  2. Przeciągnij UIScrollView do głównego widoku.

  3. Dodaj ograniczenia górne, dolne, lewe i prawe do widoku najwyższego poziomu, aby UIScrollView wypełniał cały widok główny.

  4. Dodaj UIView wewnątrz UIScrollView i nazwij go „kontener”. Dodaj górne, dolne, lewe i prawe ograniczenia do UIScrollView (jego rodzica). KLUCZOWA sztuczka: dodaj również ograniczenia „Równe szerokości”, aby połączyć UIScrollView i UIView.

    UWAGA: Pojawi się błąd „widok przewijania ma niejednoznaczną przewijalną wysokość zawartości” i że UIView kontenera powinien mieć wysokość 0 pikseli. Żaden błąd nie wydaje się mieć znaczenia, gdy aplikacja jest uruchomiona.

  5. Utwórz pliki stalówki i kontrolery dla każdej z „komórek”. Użyj UIView nie UITableViewCell.

  6. W głównym katalogu ViewController zasadniczo dodajesz wszystkie „wiersze” do UIView kontenera i programowo dodajesz ograniczenia łączące ich lewą i prawą krawędź z widokiem kontenera, ich górne krawędzie albo z góry kontenera widoku (dla pierwszego elementu), albo z poprzedniego komórka. Następnie połącz ostatnią komórkę z dnem pojemnika.

Dla nas każdy „wiersz” znajduje się w pliku stalówki. Więc kod wygląda mniej więcej tak:

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {

        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

A oto kod dla UITools.addViewToTop:

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)

        //Set left and right constraints so fills full horz width:

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))

        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

Jedyną „gotcha”, jaką do tej pory znalazłem w tym podejściu, jest to, że UITableView ma przyjemną funkcję „pływających” nagłówków sekcji u góry widoku podczas przewijania. Powyższe rozwiązanie tego nie zrobi, chyba że dodasz więcej programowania, ale w naszym konkretnym przypadku ta funkcja nie była w 100% niezbędna i nikt nie zauważył, gdy odszedł.

Jeśli chcesz dzielniki między komórkami, po prostu dodaj UIView o wysokości 1 piksela na dole niestandardowej „komórki”, która wygląda jak dzielnik.

Pamiętaj, aby włączyć „odbijanie” i „odbijanie w pionie”, aby kontrola odświeżania działała, dzięki czemu wygląda bardziej jak widok tabeli.

TableView pokazuje niektóre puste wiersze i przekładki pod twoją treścią, jeśli nie wypełnia pełnego ekranu, w przeciwieństwie do tego rozwiązania. Ale osobiście wolę, żeby te puste rzędy i tak nie były - przy zmiennej wysokości komórki i tak zawsze wydawało mi się to „błędne”, żeby mieć tam puste rzędy.

Mam nadzieję, że jakiś inny programista przeczyta mój post PRZED marnowaniem ponad 20 godzin, próbując to rozgryźć w widoku aplikacji we własnej aplikacji. :)

alpsystems.com
źródło
11

Musiałem użyć widoków dynamicznych (widoków konfiguracji i ograniczeń według kodu), a kiedy chciałem ustawić szerokość etykiety preferMaxLayoutWidth, wynosił 0. Więc mam nieprawidłową wysokość komórki.

Potem dodałem

[cell layoutSubviews];

przed wykonaniem

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

Po tym szerokość etykiety była zgodna z oczekiwaniami, a wysokość dynamiczna obliczała się w prawo.

Mansurow Rusłan
źródło
9

Załóżmy, że masz komórkę z widokiem podrzędnym i chcesz, aby wysokość komórki była wystarczająco wysoka, aby obejmowała widok podrzędny + wypełnienie.

1) Ustaw dolne ograniczenie widoku podrzędnego równe cell.contentView minus pożądane wypełnienie. Nie ustawiaj ograniczeń dla samej komórki lub cell.contentView.

2) Ustaw właściwość tableView rowHeightlub tableView:heightForRowAtIndexPath:na UITableViewAutomaticDimension.

3) Ustaw właściwość tableView estimatedRowHeightlubtableView:estimatedHeightForRowAtIndexPath: najlepiej zgadnij wysokość.

Otóż ​​to.

Vadoff
źródło
7

Jeśli układasz programowo, oto, co należy wziąć pod uwagę w systemie iOS 10 za pomocą kotwic w Swift.

Istnieją trzy zasady / kroki

NUMER 1: ustaw te dwie właściwości widoku tabeli w widoku viewDidLoad, pierwsza mówi widokowi tabeli, że powinny oczekiwać dynamicznych rozmiarów w swoich komórkach, druga pozwala aplikacji obliczyć rozmiar wskaźnika paska przewijania, dzięki czemu pomaga wydajność.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMER 2: Jest to ważne, że musisz dodać widoki podrzędne do contentView komórki, a nie do widoku, a także użyć jej układu, aby zakotwiczyć widoki do góry i dołu, jest to praktyczny przykład tego, jak to zrobić.

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

Utwórz metodę, która doda widoki podrzędne i wykona układ, wywołaj ją w metodzie init.

NUMER 3: NIE WZYWAJ METODY:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

Jeśli to zrobisz, zastąpisz swoją implementację.

Postępuj zgodnie z tymi 3 zasadami dla dynamicznych komórek w widokach tabel.

tutaj jest działająca implementacja https://github.com/jamesrochabrun/MinimalViewController

James Rochabrun
źródło
w Swift 5 UITableViewAutomaticDimension przemianowano na UITableView.automaticDimension
James Rochabrun
4

Jeśli masz długi ciąg. np. taki, który nie ma podziału linii. Wtedy możesz napotkać pewne problemy.

O „domniemanej” poprawce wspomina zaakceptowana odpowiedź i kilka innych odpowiedzi. Musisz tylko dodać

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Uważam, że odpowiedź Suragha jest najbardziej kompletna i zwięzła , dlatego nie jest myląca.

Chociaż nie wyjaśniam, dlaczego te zmiany są potrzebne. Zróbmy to.

Upuść następujący kod w projekcie.

import UIKit

class ViewController: UIViewController {

    lazy var label : UILabel = {
        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.backgroundColor = .red
        lbl.textColor = .black
        return lbl
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // step0: (0.0, 0.0)
        print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step1: (29.0, 20.5)
        label.text = "hiiiii"
        print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step2: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints"
        print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step3: (992.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
        print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step4: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
        print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step5: (328.0, 61.0)
        label.numberOfLines = 0
        print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
        // ----------
        // step6: (98.5, 243.5)
        label.preferredMaxLayoutWidth = 100
        print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")

        setupLayout()
    }
    func setupLayout(){
        view.addSubview(label)
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
}

Pamiętaj, że nie dodałem żadnych ograniczeń rozmiaru . Dodałem tylko ograniczenia centerX, centerY. Ale nadal etykieta będzie miała odpowiedni rozmiar Dlaczego?

Z powodu contentSize.

Aby to lepiej przetworzyć, najpierw zachowaj krok 0, a następnie skomentuj kroki 1-6. Niech setupLayout()pobyt. Obserwuj zachowanie.

Następnie odkomentuj krok 1 i obserwuj.

Następnie odkomentuj krok 2 i obserwuj.

Rób to, dopóki nie skomentujesz wszystkich 6 kroków i nie zaobserwujesz ich zachowań.

Co można z tego wywnioskować? Jakie czynniki mogą to zmienić contenSize?

  1. Długość tekstu: jeśli masz dłuższy tekst, szerokość wewnętrznej wartości ContentSize wzrośnie
  2. Podziały linii: jeśli dodasz, \nwówczas szerokość intrinsicContentSize będzie maksymalną szerokością wszystkich linii. Jeśli jeden wiersz ma 25 znaków, inny ma 2 znaki, a drugi 21 znaków, wówczas szerokość zostanie obliczona na podstawie 25 znaków
  3. Liczba dozwolonych linii: Musisz ustawić numberOfLinessię 0w przeciwnym wypadku nie będzie mieć wiele wierszy. Twoje numberOfLinesdostosuje swoją wewnętrzną wysokość ContentSize
  4. Wprowadzanie korekt: wyobraź sobie, że na podstawie tekstu szerokość 200i wysokość wewnętrznej wartości była 100, ale chciałeś ograniczyć szerokość do kontenera etykiety, co zamierzasz zrobić? Rozwiązaniem jest ustawienie żądanej szerokości. To zrobić poprzez ustawienie preferredMaxLayoutWidthsię 130wówczas nowy intrinsicContentSize będzie mieć szerokość w przybliżeniu 130. Wysokość byłaby oczywiście większa niż 100dlatego, że potrzebujesz więcej linii. Biorąc to pod uwagę, jeśli twoje ograniczenia są ustawione poprawnie, nie będziesz musiał tego wcale używać! Więcej informacji na ten temat można znaleźć w tej odpowiedzi i jej komentarzach. Musisz użyć preferredMaxLayoutWidthtylko, jeśli nie masz ograniczeń ograniczających szerokość / wysokość, jak można by powiedzieć „nie zawijaj tekstu, chyba że przekracza onpreferredMaxLayoutWidth ”. Ale ze 100% pewnością, jeśli ustawisz wiodącym / spływu i numberOfLinesdo 0ówczesnego jesteś dobry!Krótka historia, większość odpowiedzi, które zalecają jej użycie, są NIEPRAWIDŁOWE! Nie potrzebujesz tego. Potrzebowanie go jest znakiem, że twoje ograniczenia nie są ustawione poprawnie lub po prostu nie masz ograniczeń

  5. Rozmiar czcionki: należy również pamiętać, że jeśli zwiększysz fontSize, wówczas wzrośnie wysokość intrinsicContentSize . Nie pokazałem tego w moim kodzie. Możesz spróbować tego samodzielnie.

Wróćmy do przykładu tableViewCell:

Wszystko, co musisz zrobić, to:

  • ustaw numberOfLinesna0
  • Ogranicz etykietę do marginesów / krawędzi
  • Nie ma potrzeby ustawiania preferredMaxLayoutWidth.
miód
źródło
1

W moim przypadku muszę utworzyć niestandardową komórkę z obrazem, który pochodzi z serwera i może mieć dowolną szerokość i wysokość. I dwa etykiety UIL z dynamicznym rozmiarem (zarówno szerokość, jak i wysokość)

osiągnąłem to samo tutaj w mojej odpowiedzi z autolayout i programowo:

Zasadniczo powyżej @smileyBorg odpowiedź pomogła, ale systemLayoutSizeFittingSize nigdy nie działał dla mnie, w moim podejściu:

1. Brak użycia właściwości automatycznego obliczania wysokości wiersza. 2. Bez użycia szacunkowej wysokości 3. Nie ma potrzeby niepotrzebnej aktualizacji Ograniczenia. 4. Brak użycia automatycznej preferowanej maksymalnej szerokości układu. 5. Bez użycia systemLayoutSizeFittingSize (powinien być używany, ale nie działa dla mnie, nie wiem, co robi wewnętrznie), ale zamiast tego moja metoda - (float) getViewHeight działa i wiem, co robi wewnętrznie.

Czy możliwe jest uzyskanie różnych wysokości w komórce UITableView, gdy korzystam z kilku różnych sposobów wyświetlania komórki?

Alok
źródło
1

W moim przypadku dopełnienie było spowodowane wysokościami sectionHeader i sectionFooter, gdzie storyboard pozwolił mi zmienić go na minimum 1. Więc w metodzie viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
Rashid
źródło
1

Właśnie zrobiłem jakiś głupi błąd i spróbować z wartościami 2 rowHeighta estimatedRowHeighti tak, że może zapewnić pewne wgląd debugowania:

Jeśli ustawisz oba oba LUB tylko ustawisz estimatedRowHeight, uzyskasz pożądane zachowanie:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

Sugeruje się, abyś dokładał wszelkich starań, aby uzyskać prawidłowe oszacowanie, ale wynik końcowy nie jest inny. Wpłynie to tylko na twoją wydajność.

wprowadź opis zdjęcia tutaj


Jeśli ustawisz tylko rowHeight tj. Wykonaj tylko:

tableView.rowHeight = UITableViewAutomaticDimension

Twój wynik końcowy nie byłby tak pożądany:

wprowadź opis zdjęcia tutaj


Jeśli ustawisz wartość estimatedRowHeight1 lub mniejszą, nastąpi awaria bez względu na rowHeight.

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

Wystąpił błąd z następującym komunikatem o błędzie:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type
NSException
miód
źródło
1

Jeśli chodzi o zaakceptowaną odpowiedź @smileyborg, znalazłem

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

być zawodnym w niektórych przypadkach, gdy ograniczenia są niejednoznaczne. Lepiej zmusić silnik układu do obliczenia wysokości w jednym kierunku, używając kategorii pomocnika na UIView poniżej:

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

Gdzie w: oznacza szerokość widoku tabeli

Railwayparade
źródło
0

Wystarczy dodać te dwie funkcje do kontrolera widoku, aby rozwiązać problem. Tutaj lista jest tablicą zawierającą ciąg każdego wiersza.

 func tableView(_ tableView: UITableView, 
   estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])

    return (tableView.rowHeight) 
}

func calculateHeight(inString:String) -> CGFloat
{
    let messageString = input.text
    let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]

    let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)

    let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)

    let requredSize:CGRect = rect
    return requredSize.height
}
Parth Barot
źródło
-1
swift 4

    @IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var tableView: UITableView!
    private var context = 1
 override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
    }
  // Added observer to adjust tableview height based on the content

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &self.context{
            if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
                print("-----")
                print(size.height)
                tableViewHeightConstraint.constant = size.height + 50
            }
        }
    }

//Remove observer
 deinit {

        NotificationCenter.default.removeObserver(self)

    }
Manee ios
źródło
-1

Jeśli wysokość komórki jest dynamiczna według zawartości, należy ją dokładnie policzyć, a następnie zwrócić wartość wysokości przed renderowaniem komórki. Prostym sposobem jest zdefiniowanie metody liczenia w kodzie komórki widoku tabeli, aby sterownik mógł wywoływać metodę delegowania wysokości komórki tabeli. Nie zapomnij policzyć rzeczywistej szerokości ramki komórki (domyślnie 320), jeśli wysokość zależy od szerokości stołu lub ekranu. Oznacza to, że w metodzie delegowania wysokości komórki tabeli użyj cell.frame, aby najpierw poprawić szerokość komórki, a następnie wywołaj metodę wysokości zliczania zdefiniowaną w komórce, aby uzyskać odpowiednią wartość i zwrócić ją .

PS. Kod do generowania obiektu komórki można zdefiniować w innej metodzie dla innej metody delegowania komórki widoku tabeli do wywołania.

Shrdi
źródło
-3

UITableView.automaticDimension można ustawić za pomocą Konstruktora interfejsów:

Xcode> Storyboard> Inspektor rozmiarów

Komórka widoku tabeli> Wysokość wiersza> Automatyczna

Inspektor rozmiarów

pkamb
źródło
-4

jeszcze jedno rozwiązanie iOs7 + iOs8 w Swift

var cell2height:CGFloat=44

override func viewDidLoad() {
    super.viewDidLoad()
    theTable.rowHeight = UITableViewAutomaticDimension
    theTable.estimatedRowHeight = 44.0;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
    cell2height=cell.contentView.height
    return cell
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height
    }
}
djdance
źródło
Uwaga: SystemLayoutSizeFittingSize nie działa w moim przypadku
djdance
wysokość komórki jest niepoprawna cellForRowAtIndexPath, w tym momencie komórka nie jest jeszcze ułożona.
Andrii Chernenko
w iOs7 jest to stała wartość, tzn. działa. Możesz ustawić poza komórką cellForRowAtIndexPath, jeśli chcesz
djdance 28.01.16