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 UITableViewCell
podklasy, 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:
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
updateConstraints
metody podklasy UITableViewCell. Należy pamiętać, że updateConstraints
moż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 updateConstraints
w 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 constant
właściwość w przypadku niektórych ograniczeń), umieść to w miejscu, updateConstraints
ale poza sprawdzaniem, didSetupConstraints
aby 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ć rowHeight
wł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 estimatedRowHeight
niezerową 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ść contentView
musi 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ć estimatedRowHeight
właściwość w widoku tabeli (w viewDidLoad
lub 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
, contentView
aby dowiedzieć się, jaka jest wymagana wysokość komórki. Użyj, UILayoutFittingCompressedSize
aby 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 estimatedRowHeight
wł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ć estimatedRowHeight
właściwość w widoku tabeli (w viewDidLoad
lub 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 .