TL; DR: Lesen Sie nicht gern? Wechseln Sie direkt zu den Beispielprojekten auf GitHub:
Konzeptionelle Beschreibung
Die ersten beiden Schritte gelten unabhängig davon, für welche iOS-Versionen Sie entwickeln.
1. Einrichten und Hinzufügen von Einschränkungen
UITableViewCell
Fügen Sie in Ihrer Unterklasse Einschränkungen hinzu, sodass die Kanten der Unteransichten der Zelle an den Rändern der Inhaltsansicht der Zelle befestigt sind (am wichtigsten an den oberen UND unteren Rändern). HINWEIS: Pin-Unteransichten nicht an die Zelle selbst anheften. nur zu den Zellen contentView
! Lassen Sie die intrinsische Inhaltsgröße dieser Unteransichten die Höhe der Inhaltsansicht der Tabellenansichtszelle bestimmen, indem Sie sicherstellen, dass der Inhaltskomprimierungswiderstand und die inhaltlichen Einschränkungen in der vertikalen Dimension für jede Unteransicht nicht durch von Ihnen hinzugefügte Einschränkungen mit höherer Priorität überschrieben werden. ( Huh? Hier klicken. )
Denken Sie daran, dass die Unteransichten der Zelle vertikal mit der Inhaltsansicht der Zelle verbunden werden sollen, damit sie "Druck ausüben" und die Inhaltsansicht entsprechend erweitern können. Anhand einer Beispielzelle mit einigen Unteransichten sehen Sie hier visuell, wie einige (nicht alle!) Ihrer Einschränkungen aussehen müssten:
Sie können sich vorstellen, dass beim Hinzufügen von mehr Text zur mehrzeiligen Körperbezeichnung in der obigen Beispielzelle diese vertikal wachsen muss, um dem Text zu entsprechen, wodurch die Zelle effektiv an Höhe zunimmt. (Natürlich müssen Sie die Einschränkungen richtig einstellen, damit dies richtig funktioniert!)
Die richtigen Einschränkungen sind definitiv der schwierigste und wichtigste Teil , um dynamische Zellenhöhen mit Auto Layout zu erreichen. Wenn Sie hier einen Fehler machen, kann dies dazu führen, dass alles andere nicht mehr funktioniert - nehmen Sie sich also Zeit! Ich empfehle, Ihre Einschränkungen im Code einzurichten, da Sie genau wissen, welche Einschränkungen wo hinzugefügt werden, und es viel einfacher ist, Fehler zu beheben, wenn etwas schief geht. Das Hinzufügen von Einschränkungen im Code kann genauso einfach und wesentlich leistungsfähiger sein als Interface Builder mit Layout-Ankern oder einer der fantastischen Open Source-APIs, die auf GitHub verfügbar sind.
- Wenn Sie dem Code Einschränkungen hinzufügen, sollten Sie dies einmal in der
updateConstraints
Methode Ihrer UITableViewCell-Unterklasse tun. Beachten Sie, dass dies updateConstraints
möglicherweise mehrmals aufgerufen wird. Um zu vermeiden, dass dieselben Einschränkungen mehrmals hinzugefügt werden, sollten Sie Ihren Code zum Hinzufügen von Einschränkungen updateConstraints
in eine Prüfung auf eine boolesche Eigenschaft wie didSetupConstraints
(die Sie nach dem Ausführen Ihrer Einschränkung auf YES setzen) einschließen - Code einmal hinzufügen). Wenn Sie jedoch Code haben, der vorhandene Einschränkungen aktualisiert (z. B. das Anpassen der constant
Eigenschaft für einige Einschränkungen), platzieren Sie diesen in, updateConstraints
aber außerhalb der Prüfung, didSetupConstraints
damit er bei jedem Aufruf der Methode ausgeführt werden kann.
2. Bestimmen Sie eindeutige Kennungen für die Wiederverwendung von Zellen in der Tabellenansicht
Verwenden Sie für jeden eindeutigen Satz von Einschränkungen in der Zelle eine eindeutige Kennung für die Wiederverwendung von Zellen. Mit anderen Worten, wenn Ihre Zellen mehr als ein eindeutiges Layout haben, sollte jedes eindeutige Layout eine eigene Wiederverwendungskennung erhalten. (Ein guter Hinweis, dass Sie eine neue Wiederverwendungskennung verwenden müssen, ist, wenn Ihre Zellenvariante eine andere Anzahl von Unteransichten aufweist oder die Unteransichten unterschiedlich angeordnet sind.)
Wenn Sie beispielsweise in jeder Zelle eine E-Mail-Nachricht anzeigen, haben Sie möglicherweise 4 eindeutige Layouts: Nachrichten mit nur einem Betreff, Nachrichten mit einem Betreff und einem Textkörper, Nachrichten mit einem Betreff und einem Fotoanhang sowie Nachrichten mit einem Betreff. Körper und Fotoaufsatz. Für jedes Layout sind völlig andere Einschränkungen erforderlich. Sobald die Zelle initialisiert und die Einschränkungen für einen dieser Zelltypen hinzugefügt wurden, sollte die Zelle eine eindeutige Wiederverwendungskennung erhalten, die für diesen Zelltyp spezifisch ist. Das heißt, wenn Sie eine Zelle zur Wiederverwendung aus der Warteschlange entfernen, wurden die Einschränkungen bereits hinzugefügt und können für diesen Zelltyp verwendet werden.
Beachten Sie, dass Zellen mit denselben Einschränkungen (Typ) aufgrund der unterschiedlichen Größe des Inhalts möglicherweise immer noch unterschiedliche Höhen haben! Verwechseln Sie nicht grundlegend unterschiedliche Layouts (unterschiedliche Einschränkungen) mit unterschiedlichen berechneten Ansichtsrahmen (gelöst aus identischen Einschränkungen) aufgrund unterschiedlicher Inhaltsgrößen.
- Fügen Sie dem gleichen Wiederverwendungspool keine Zellen mit völlig unterschiedlichen Einschränkungen hinzu (dh verwenden Sie dieselbe Wiederverwendungskennung), und versuchen Sie dann, die alten Einschränkungen zu entfernen und nach jeder Warteschlange neue Einschränkungen von Grund auf neu einzurichten. Die interne Auto-Layout-Engine ist nicht für große Änderungen von Einschränkungen ausgelegt, und es treten massive Leistungsprobleme auf.
Für iOS 8 - Zellen mit Selbstgröße
3. Aktivieren Sie die Zeilenhöhenschätzung
Um Zellen der Tabellengröße mit Selbstgröße zu aktivieren, müssen Sie die rowHeight-Eigenschaft der Tabellenansicht auf UITableViewAutomaticDimension festlegen. Sie müssen der geschätztenRowHeight-Eigenschaft auch einen Wert zuweisen. Sobald diese beiden Eigenschaften festgelegt sind, berechnet das System mithilfe des automatischen Layouts die tatsächliche Höhe der Zeile
Apple: Arbeiten mit Zellen der Tabellengröße mit Selbstgröße
Mit iOS 8 hat Apple einen Großteil der Arbeit verinnerlicht, die zuvor von Ihnen vor iOS 8 implementiert werden musste. Damit der Mechanismus für Zellen mit Selbstgröße funktioniert, müssen Sie zuerst die rowHeight
Eigenschaft in der Tabellenansicht auf die Konstante setzen UITableViewAutomaticDimension
. Anschließend müssen Sie lediglich die Zeilenhöhenschätzung aktivieren, indem Sie die estimatedRowHeight
Eigenschaft der Tabellenansicht auf einen Wert ungleich Null setzen. Beispiel:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
Dadurch wird der Tabellenansicht eine temporäre Schätzung / ein Platzhalter für die Zeilenhöhen von Zellen bereitgestellt, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen auf dem Bildschirm gescrollt werden, wird die tatsächliche Zeilenhöhe berechnet. Um die tatsächliche Höhe für jede Zeile zu bestimmen, fragt die Tabellenansicht jede Zelle automatisch, welche Höhe sie contentView
benötigt, basierend auf der bekannten festen Breite der Inhaltsansicht (die auf der Breite der Tabellenansicht basiert, abzüglich zusätzlicher Elemente wie eines Abschnittsindex) oder Zubehöransicht) und die Einschränkungen für das automatische Layout, die Sie der Inhaltsansicht und den Unteransichten der Zelle hinzugefügt haben. Sobald diese tatsächliche Zellenhöhe ermittelt wurde, wird die alte geschätzte Höhe für die Zeile mit der neuen tatsächlichen Höhe aktualisiert (und alle Anpassungen an contentSize / contentOffset der Tabellenansicht werden nach Bedarf für Sie vorgenommen).
Im Allgemeinen muss die von Ihnen angegebene Schätzung nicht sehr genau sein. Sie wird nur verwendet, um die Größe der Bildlaufanzeige in der Tabellenansicht korrekt zu bestimmen, und die Tabellenansicht passt die Bildlaufanzeige gut an, wenn Sie falsche Schätzungen vornehmen Bildlaufzellen auf dem Bildschirm. Sie sollten die estimatedRowHeight
Eigenschaft in der Tabellenansicht (in viewDidLoad
oder ähnlich) auf einen konstanten Wert setzen, der der "durchschnittlichen" Zeilenhöhe entspricht. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung unterschiedlich sind) und Sie beim Scrollen die Bildlaufanzeige "springen" bemerken, sollten Sie sich die Mühe tableView:estimatedHeightForRowAtIndexPath:
machen, die minimale Berechnung durchzuführen , die erforderlich ist, um eine genauere Schätzung für jede Zeile zurückzugeben.
Für iOS 7-Unterstützung (Implementierung der automatischen Zellengröße selbst)
3. Führen Sie einen Layout-Pass durch und ermitteln Sie die Zellenhöhe
Instanziieren Sie zunächst eine Offscreen-Instanz einer Tabellenansichtszelle, eine Instanz für jede Wiederverwendungskennung , die ausschließlich für Höhenberechnungen verwendet wird. (Offscreen bedeutet, dass die Zellreferenz in einer Eigenschaft / ivar auf dem Ansichtscontroller gespeichert und nie zurückgegeben wird, tableView:cellForRowAtIndexPath:
damit die Tabellenansicht tatsächlich auf dem Bildschirm gerendert wird.) Als Nächstes muss die Zelle mit dem genauen Inhalt (z. B. Text, Bilder usw.) konfiguriert werden. dass es gelten würde, wenn es in der Tabellenansicht angezeigt würde.
Erzwingen Sie dann, dass die Zelle ihre Unteransichten sofort anordnet, und verwenden Sie dann die systemLayoutSizeFittingSize:
Methode auf den UITableViewCell
's contentView
, um herauszufinden, wie hoch die erforderliche Höhe der Zelle ist. Verwenden Sie UILayoutFittingCompressedSize
diese Option , um die kleinste Größe zu erhalten, die für den gesamten Inhalt der Zelle erforderlich ist. Die Höhe kann dann von der tableView:heightForRowAtIndexPath:
Delegatmethode zurückgegeben werden.
4. Verwenden Sie Geschätzte Zeilenhöhen
Wenn Ihre Tabellenansicht mehr als ein paar Dutzend Zeilen enthält, werden Sie feststellen, dass das Lösen von Auto-Layout-Einschränkungen beim ersten Laden der Tabellenansicht den Hauptthread schnell blockieren kann, wie dies tableView:heightForRowAtIndexPath:
für jede einzelne Zeile beim ersten Laden aufgerufen wird ( um die Größe der Bildlaufanzeige zu berechnen).
Ab iOS 7 können (und sollten) Sie die estimatedRowHeight
Eigenschaft in der Tabellenansicht verwenden. Dadurch wird der Tabellenansicht eine temporäre Schätzung / ein Platzhalter für die Zeilenhöhen von Zellen bereitgestellt, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen auf dem Bildschirm gescrollt werden, wird die tatsächliche Zeilenhöhe berechnet (durch Aufrufen tableView:heightForRowAtIndexPath:
) und die geschätzte Höhe mit der tatsächlichen aktualisiert.
Im Allgemeinen muss die von Ihnen angegebene Schätzung nicht sehr genau sein. Sie wird nur verwendet, um die Größe der Bildlaufanzeige in der Tabellenansicht korrekt zu bestimmen, und die Tabellenansicht passt die Bildlaufanzeige gut an, wenn Sie falsche Schätzungen vornehmen Bildlaufzellen auf dem Bildschirm. Sie sollten die estimatedRowHeight
Eigenschaft in der Tabellenansicht (in viewDidLoad
oder ähnlich) auf einen konstanten Wert setzen, der der "durchschnittlichen" Zeilenhöhe entspricht. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung unterschiedlich sind) und Sie beim Scrollen die Bildlaufanzeige "springen" bemerken, sollten Sie sich die Mühe tableView:estimatedHeightForRowAtIndexPath:
machen, die minimale Berechnung durchzuführen , die erforderlich ist, um eine genauere Schätzung für jede Zeile zurückzugeben.
5. (Falls erforderlich) Zeilenhöhen-Caching hinzufügen
Wenn Sie alle oben genannten Schritte ausgeführt haben und immer noch feststellen, dass die Leistung beim Lösen von Einschränkungen inakzeptabel langsam ist tableView:heightForRowAtIndexPath:
, müssen Sie leider ein gewisses Caching für Zellenhöhen implementieren. (Dies ist der von den Apple-Ingenieuren vorgeschlagene Ansatz.) Die allgemeine Idee besteht darin, die Autolayout-Engine die Einschränkungen beim ersten Mal lösen zu lassen, dann die berechnete Höhe für diese Zelle zwischenzuspeichern und den zwischengespeicherten Wert für alle zukünftigen Anforderungen für die Höhe dieser Zelle zu verwenden. Der Trick besteht natürlich darin, sicherzustellen, dass Sie die zwischengespeicherte Höhe für eine Zelle löschen, wenn etwas passiert, das dazu führen kann, dass sich die Höhe der Zelle ändert. Dies ist in erster Linie dann der Fall, wenn sich der Inhalt dieser Zelle ändert oder wenn andere wichtige Ereignisse auftreten (z. B. das Anpassen durch den Benutzer) den Schieberegler für die Textgröße "Dynamischer Typ").
Generischer Beispielcode für iOS 7 (mit vielen saftigen Kommentaren)
- (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;
}
}
Beispielprojekte
Diese Projekte sind voll funktionsfähige Beispiele für Tabellenansichten mit variablen Zeilenhöhen aufgrund von Tabellenansichtszellen, die dynamischen Inhalt in UILabels enthalten.
Xamarin (C # /. NET)
Wenn Sie Xamarin verwenden, check this out Beispielprojekt zusammengestellt von @KentBoogaart .