Frage Verwenden von Auto Layout in UITableView für dynamische Zellenlayouts und variable Zeilenhöhen


Wie verwenden Sie Auto-Layout innerhalb UITableViewCells in einer Tabellenansicht, damit der Inhalt und die Untersichten jeder Zelle die Zeilenhöhe (selbst / automatisch) bestimmen, während die reibungslose Scrollleistung erhalten bleibt?


1350
2017-09-11 16:48


Ursprung


Antworten:


TL; DR: Mag es nicht zu lesen? Gehe direkt zu den Beispielprojekten auf GitHub:

Begriffsbeschreibung

Die ersten beiden Schritte gelten unabhängig davon, für welche iOS-Versionen Sie entwickeln.

1. Einrichten und Hinzufügen von Einschränkungen

In deinem UITableViewCell Unterklasse, fügen Sie Einschränkungen hinzu, so dass die Kanten der Unteransichten der Zelle an den Kanten der Zellen angeheftet sind Inhaltsansicht (am wichtigsten zu den oberen UND unteren Kanten). HINWEIS: Unteransichten nicht an die Zelle selbst anhängen; nur zu den Zellen contentView! Lassen Sie die Inhaltsgröße dieser Subviews die Höhe der Inhaltsansicht der Tabellenansichtszelle bestimmen, indem Sie sicherstellen, dass die Inhaltskompressionsbeständigkeit und Inhalt umarmen Constraints in der vertikalen Dimension für jede Unteransicht werden nicht durch Constraints höherer Priorität überschrieben, die Sie hinzugefügt haben. (Hä? Klick hier.)

Denken Sie daran, dass die Unteransichten der Zelle vertikal mit der Inhaltsansicht der Zelle verbunden sein müssen, damit sie "Druck ausüben" und die Inhaltsansicht erweitern können, um sie anzupassen. Unter Verwendung einer Beispielzelle mit einigen Unteransichten wird hier eine visuelle Illustration von was gezeigt etwas  (nicht alle!) Ihrer Einschränkungen müssten aussehen:

Example illustration of constraints on a table view cell.

Sie können sich vorstellen, dass mehr Text zur mehrzeiligen Textbeschriftung in der obigen Beispielzelle hinzugefügt werden muss, damit er vertikal wachsen kann, damit er in die Höhe passt. (Natürlich müssen Sie die Einschränkungen richtig machen, damit dies richtig funktioniert!)

Deine Einschränkungen richtig zu machen ist definitiv die am schwersten und wichtigsten Teil dynamischer Zellenhöhen, die mit Auto Layout arbeiten. Wenn Sie hier einen Fehler machen, könnte das alles daran hindern, zu funktionieren - nehmen Sie sich also Zeit! Ich empfehle, die Einschränkungen in Code zu definieren, weil Sie genau wissen, welche Einschränkungen wo hinzugefügt werden, und es ist viel einfacher zu debuggen, wenn etwas schief geht. Das Hinzufügen von Constraints im Code kann genauso einfach und wesentlich leistungsfähiger sein als der Interface Builder, der Layout-Anker verwendet, oder eine der fantastischen Open-Source-APIs, die auf GitHub verfügbar sind.

  • Wenn Sie Einschränkungen im Code hinzufügen, sollten Sie dies einmal innerhalb von. Tun updateConstraints Methode Ihrer UITableViewCell-Unterklasse. Beachten Sie, dass updateConstraints kann mehr als einmal aufgerufen werden. Um zu vermeiden, dass dieselben Einschränkungen mehr als einmal hinzugefügt werden, müssen Sie sicherstellen, dass Sie den Code, der die Integritätsregel hinzufügt, in den Code einfügen updateConstraints in einer Überprüfung für eine boolesche Eigenschaft wie didSetupConstraints (die Sie auf YES setzen, nachdem Sie den constraint-addierenden Code einmal ausgeführt haben). Wenn Sie jedoch über Code verfügen, der vorhandene Einschränkungen aktualisiert (z. B. Anpassen der constantEigenschaft für einige Einschränkungen), legen Sie dies in updateConstraints aber außerhalb des Checks für didSetupConstraints So kann es jedes Mal ausgeführt werden, wenn die Methode aufgerufen wird.

2. Ermitteln Sie die eindeutigen Wiederverwendungsidentifizierer für Tabellenansichtszellen

Verwenden Sie für jeden eindeutigen Satz von Integritätsbedingungen in der Zelle einen eindeutigen Zellwiederverwendungsbezeichner. 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 Wiederverwendungs-ID verwenden müssen, ist, wenn Ihre Zellenvariante eine andere Anzahl von Untersichten aufweist oder die Untersichten in einer anderen Art angeordnet sind.)

Wenn Sie beispielsweise in jeder Zelle eine E-Mail-Nachricht anzeigen, verfügen Sie möglicherweise über vier eindeutige Layouts: Nachrichten mit nur einem Betreff, Nachrichten mit einem Betreff und einem Text, Nachrichten mit einem Betreff und einem Fotoanhang sowie Nachrichten mit einem Betreff. Körper und Fotoanhang. Jedes Layout hat völlig andere Einschränkungen, die erforderlich sind, um es zu erreichen. Sobald die Zelle initialisiert ist und die Beschränkungen für einen dieser Zelltypen hinzugefügt sind, sollte die Zelle eine eindeutige Wiederverwendungskennung erhalten, die für diesen Zelltyp spezifisch ist. Das bedeutet, wenn Sie eine Zelle zur Wiederverwendung aus der Warteschlange entfernen, wurden die Einschränkungen bereits hinzugefügt und sind bereit für diesen Zelltyp zu gehen.

Beachten Sie, dass Zellen mit denselben Einschränkungen (Typ) aufgrund von Unterschieden in der Größe des intrinsischen Inhalts immer noch unterschiedliche Höhen haben können! Verwechseln Sie grundsätzlich unterschiedliche Layouts (unterschiedliche Constraints) nicht mit unterschiedlichen berechneten View-Frames (die von identischen Constraints gelöst werden) aufgrund unterschiedlicher Inhaltsgrößen.

  • Fügen Sie dem gleichen Wiederverwendungspool keine Zellen mit vollständig unterschiedlichen Sätzen von Beschränkungen hinzu (d. H. Verwenden Sie den gleichen Wiederverwendungsidentifizierer), und versuchen Sie dann, die alten Einschränkungen zu entfernen und neue Beschränkungen von Grund auf neu zu erstellen. Die interne Auto-Layout-Engine ist nicht dafür ausgelegt, große Änderungen der Einschränkungen zu verarbeiten, und Sie werden massive Leistungsprobleme feststellen.

Für iOS 8 - Self-Sizing-Zellen

3. Aktivieren Sie Zeilenhöhenschätzung

Um Zellen mit selbstanpassender Tabellenansicht zu aktivieren, müssen Sie die Tabellenansicht festlegen   rowHeight-Eigenschaft zu UITableViewAutomaticDimension. Sie müssen auch   Weisen Sie der Eigenschaft estimatedRowHeight einen Wert zu. Sobald beide   Wenn diese Eigenschaften festgelegt sind, berechnet das System automatisches Layout   die tatsächliche Höhe der Reihe

Apfel: Arbeiten mit Self-Sizing-Tabellenansichtszellen

Mit iOS 8 hat Apple einen großen Teil der Arbeit verinnerlicht, die zuvor von Ihnen vor iOS 8 implementiert werden musste. Damit der Self-Sizing-Zellenmechanismus funktionieren kann, müssen Sie zunächst den Wert für rowHeight Eigenschaft in der Tabellenansicht auf die Konstante UITableViewAutomaticDimension. Dann müssen Sie nur die Zeilenhöhenschätzung aktivieren, indem Sie die Tabellenansicht festlegen estimatedRowHeight Eigenschaft auf einen Wert ungleich Null, zum Beispiel:

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

Dadurch erhält die Tabellenansicht eine temporäre Schätzung / einen Platzhalter für die Zeilenhöhen von Zellen, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen gerade auf dem Bildschirm rollen, wird die tatsächliche Zeilenhöhe berechnet. Um die tatsächliche Höhe für jede Zeile zu bestimmen, fragt die Tabellenansicht automatisch jede Zelle nach ihrer Höhe contentViewmuss auf der bekannten festen Breite der Inhaltsansicht (die auf der Breite der Tabellenansicht basiert, abzüglich zusätzlicher Elemente wie einem Abschnittsindex oder einer Zubehöransicht) und den automatischen Layoutbeschränkungen, die Sie der Inhaltsansicht und den Unteransichten der Zelle hinzugefügt haben, basieren . Sobald diese tatsächliche Zellenhöhe bestimmt wurde, wird die alte geschätzte Höhe für die Zeile mit der neuen tatsächlichen Höhe aktualisiert (und alle Anpassungen der contentSize / contentOffset der Tabellenansicht werden nach Bedarf für Sie vorgenommen).

Im Allgemeinen muss die von Ihnen bereitgestellte Schätzung nicht sehr genau sein - sie wird nur zur korrekten Größenanpassung des Bildlaufanzeigers in der Tabellenansicht verwendet, und in der Tabellenansicht wird der Bildlaufanzeiger für falsche Schätzungen genauso angepasst wie für Sie Scroll-Zellen auf dem Bildschirm. Du solltest das einstellen estimatedRowHeight Eigenschaft in der Tabellenansicht (in viewDidLoad oder ähnlich) zu einem konstanten Wert, der die "durchschnittliche" Zeilenhöhe ist. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung) und Sie den Scrollindikator "Springen" beim Scrollen bemerken, sollten Sie die Implementierung übernehmen tableView:estimatedHeightForRowAtIndexPath: um die minimale Berechnung durchzuführen, die erforderlich ist, um eine genauere Schätzung für jede Zeile zu liefern.

Für iOS 7-Unterstützung (Implementieren der automatischen Zellengröße selbst)

3. Führen Sie ein Layout aus und erhalten Sie die Zellenhöhe

Instanziieren Sie zuerst eine Offscreen-Instanz einer Tabellenansichtszelle eine Instanz für jede Wiederverwendungskennung, das wird ausschließlich für Höhenberechnungen verwendet. (Offscreen bedeutet, dass die Zellreferenz in einer Eigenschaft / einem Ivar auf dem View-Controller gespeichert ist und nie von 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, den sie hätte, wenn sie in der Tabellenansicht angezeigt werden würde.

Fordern Sie dann die Zelle auf, die Unteransichten sofort zu planen, und verwenden Sie dann die systemLayoutSizeFittingSize: Methode auf der UITableViewCellist es contentView um herauszufinden, wie hoch die benötigte Höhe der Zelle ist. Benutzen UILayoutFittingCompressedSize um die kleinste Größe zu erhalten, die erforderlich ist, um alle Inhalte der Zelle anzupassen. Die Höhe kann dann von der zurückgegeben werden tableView:heightForRowAtIndexPath: Delegierte Methode.

4. Verwenden Sie geschätzte Zeilenhöhen

Wenn Ihre Tabellenansicht mehr als ein paar Dutzend Zeilen enthält, werden Sie feststellen, dass die automatische Layout-Abhängigkeitslösung den Hauptthread beim ersten Laden der Tabellenansicht schnell verwischen kann tableView:heightForRowAtIndexPath: wird beim ersten Laden jeder einzelnen Zeile aufgerufen (um die Größe des Scrollindikators zu berechnen).

Ab iOS 7 können (und sollten Sie unbedingt) das estimatedRowHeight Eigenschaft in der Tabellenansicht. Dadurch erhält die Tabellenansicht eine temporäre Schätzung / einen Platzhalter für die Zeilenhöhen von Zellen, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen gerade auf dem Bildschirm rollen, wird die tatsächliche Zeilenhöhe berechnet (durch Aufruf von tableView:heightForRowAtIndexPath:), und die geschätzte Höhe wird mit der tatsächlichen aktualisiert.

Im Allgemeinen muss die von Ihnen bereitgestellte Schätzung nicht sehr genau sein - sie wird nur zur korrekten Größenanpassung des Bildlaufanzeigers in der Tabellenansicht verwendet, und in der Tabellenansicht wird der Bildlaufanzeiger für falsche Schätzungen genauso angepasst wie für Sie Scroll-Zellen auf dem Bildschirm. Du solltest das einstellen estimatedRowHeight Eigenschaft in der Tabellenansicht (in viewDidLoad oder ähnlich) zu einem konstanten Wert, der die "durchschnittliche" Zeilenhöhe ist. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung) und Sie den Scrollindikator "Springen" beim Scrollen bemerken, sollten Sie die Implementierung übernehmen tableView:estimatedHeightForRowAtIndexPath: um die minimale Berechnung durchzuführen, die erforderlich ist, um eine genauere Schätzung für jede Zeile zu liefern.

5. (Wenn erforderlich) Zeilenhöhen-Caching hinzufügen

Wenn Sie alle oben genannten Schritte durchgeführt haben und immer noch feststellen, dass die Leistung inakzeptabel langsam ist, wenn Sie die Integritätsbedingung lösen tableView:heightForRowAtIndexPath:, müssen Sie leider etwas Caching für Zellenhöhen implementieren. (Dies ist der von den Ingenieuren von Apple vorgeschlagene Ansatz.) Die allgemeine Idee ist, dass die Auto-Layout-Engine die Beschränkungen beim ersten Mal löst, dann die berechnete Höhe für diese Zelle zwischenspeichert und den zwischengespeicherten Wert für alle zukünftigen Anforderungen für die Höhe dieser Zelle verwendet. Der Trick besteht natürlich darin, sicherzustellen, dass Sie die zwischengespeicherte Höhe für eine Zelle löschen, wenn etwas passiert, das die Höhe der Zelle ändern könnte - in erster Linie, wenn sich der Inhalt dieser Zelle ändert oder wenn andere wichtige Ereignisse eintreten (wie der Benutzer anpasst) den Schieberegler für den Schriftgrad des dynamischen Typs).

iOS 7 Generic Sample Code (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 multi-line 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, überprüfen Sie dies Beispielprojekt zusammengestellt von @KentBoogaart.


2265
2018-06-19 08:21



Für IOS8 ist es wirklich einfach:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

ODER

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

Aber für IOS7 ist der Schlüssel die Höhe nach dem automatischen Layout berechnen,

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

Wichtig 

  • Wenn mehrere Zeilen Etiketten, vergessen Sie nicht, die numberOfLines zu 0.

  • Vergiss nicht label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

Der vollständige Beispielcode ist Hier.


122
2018-04-05 09:23



Schnelles Beispiel einer UITableViewCell mit variabler Höhe

Für Swift 3 aktualisiert

William Hus schnelle Antwort ist gut, aber es hilft mir, ein paar einfache, aber detaillierte Schritte zu machen, wenn ich etwas zum ersten Mal lernen muss. Das folgende Beispiel ist mein Testprojekt, während ich lerne, ein UITableView mit variablen Zellenhöhen. Ich basierte darauf Dieses grundlegende UITableView-Beispiel für Swift.

Das fertige Projekt sollte so aussehen:

enter image description here

Erstellen Sie ein neues Projekt

Es kann nur eine Single-View-Anwendung sein.

Fügen Sie den Code hinzu

Fügen Sie Ihrem Projekt eine neue Swift-Datei hinzu. Nennen Sie es MyCustomCell. Diese Klasse enthält die Ausgänge für die Ansichten, die Sie Ihrer Zelle im Storyboard hinzufügen. In diesem grundlegenden Beispiel haben wir nur eine Bezeichnung in jeder Zelle.

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

Wir werden diese Steckdose später anschließen.

Öffnen Sie ViewController.swift und stellen Sie sicher, dass Sie folgenden Inhalt haben:

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).")
    }
}

Wichtige Notiz:

  • Es sind die folgenden zwei Codezeilen (zusammen mit dem automatischen Layout), die die variable Zellenhöhe ermöglichen:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Richte das Storyboard ein

Fügen Sie Ihrem View-Controller eine Tabellenansicht hinzu und verwenden Sie das automatische Layout, um es an die vier Seiten zu heften. Ziehen Sie dann eine Tabellenansichtszelle in die Tabellenansicht. Ziehen Sie in der Zelle Prototype ein Label. Verwenden Sie das automatische Layout, um die Beschriftung an die vier Kanten der Inhaltsansicht der Tabellenansichtszelle anzuheften.

enter image description here

Wichtige Notiz:

  • Das automatische Layout funktioniert zusammen mit den oben erwähnten wichtigen zwei Codezeilen. Wenn Sie kein automatisches Layout verwenden, wird es nicht funktionieren.

Andere IB Einstellungen

Name und Kennung der benutzerdefinierten Klasse

Wählen Sie die Tabellenansichtszelle aus, und legen Sie die benutzerdefinierte Klasse fest MyCustomCell (Der Name der Klasse in der Swift-Datei, die wir hinzugefügt haben). Stellen Sie auch den Identifier ein cell(die gleiche Zeichenfolge, die wir für die verwendet haben cellReuseIdentifier im obigen Code.

enter image description here

Null Zeilen für Label

Stellen Sie die Anzahl der Zeilen auf ein 0 in deinem Etikett. Dies bedeutet mehrzeilig und ermöglicht es dem Label, sich basierend auf seinem Inhalt selbst zu skalieren.

enter image description here 

Schließen Sie die Ausgänge an

  • Steuern Sie den Mauszeiger von der Tabellenansicht im Storyboard auf die tableView Variable in der ViewController Code.
  • Tun Sie dasselbe für das Label in Ihrer Prototype - Zelle myCellLabel Variable in der MyCustomCell Klasse.

Fertig

Sie sollten jetzt in der Lage sein, Ihr Projekt auszuführen und Zellen mit variablen Höhen zu erhalten.

Anmerkungen

  • Dieses Beispiel funktioniert nur für iOS 8 und danach. Wenn Sie iOS 7 weiterhin unterstützen müssen, funktioniert dies nicht für Sie.
  • Ihre eigenen benutzerdefinierten Zellen in Ihren zukünftigen Projekten werden wahrscheinlich mehr als nur ein Label haben. Stellen Sie sicher, dass Sie alles richtig angeheftet haben, damit das automatische Layout die richtige Höhe bestimmen kann. Sie müssen möglicherweise auch vertikalen Kompressionswiderstand und Umarmungen verwenden. Sehen Dieser Artikel dazu mehr.
  • Wenn Sie die Vorder- und Hinterkante (links und rechts) nicht fixieren, müssen Sie möglicherweise auch die Beschriftung festlegen preferredMaxLayoutWidth damit es weiß, wann der Zeilenumbruch erfolgen soll. Wenn Sie beispielsweise der Beschriftung im Projekt oben eine Beschränkung "Mitte horizontal" hinzugefügt haben, anstatt die Vorder- und Hinterkante festzulegen, müssen Sie diese Zeile zu der Option hinzufügen tableView:cellForRowAtIndexPath Methode:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Siehe auch


71
2018-06-11 15:18



Ich habe @ smileyborgs iOS7-Lösung in eine Kategorie eingepackt

Ich habe mich entschieden, diese clevere Lösung von @smileyborg in eine UICollectionViewCell+AutoLayoutDynamicHeightCalculation Kategorie.

Die Kategorie behebt auch die Probleme, die in @ wildmonkey's Antwort (Laden einer Zelle aus einer Feder und systemLayoutSizeFittingSize: Rückkehr CGRectZero)

Es berücksichtigt keine Zwischenspeicherung, aber passt jetzt zu meinen Bedürfnissen. Fühlen Sie sich frei, es zu kopieren, einzufügen und zu hacken.

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

Verwendungsbeispiel:

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

Zum Glück müssen wir diesen Jazz in iOS8 nicht machen, aber da ist es jetzt!


60
2018-02-10 19:41



Hier ist meine Lösung. Sie müssen der TableView die geschätzte Höhe mitteilen, bevor sie die Ansicht lädt. Sonst wird es sich nicht wie erwartet verhalten können.

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

43
2017-11-11 16:52



Die von @smileyborg vorgeschlagene Lösung ist nahezu perfekt. Wenn Sie eine benutzerdefinierte Zelle haben und eines oder mehrere möchten UILabel mit dynamischen Höhen dann die systemLayoutSizeFittingSize Methode kombiniert mit AutoLayout aktiviert gibt a zurück CGSizeZero Es sei denn, Sie verschieben alle Zellenbeschränkungen von der Zelle in die contentView (wie von @TomSwift hier vorgeschlagen) Wie ändert man die Größe von Superview, um alle Subviews mit automatischem Layout anzupassen?).

Dazu müssen Sie den folgenden Code in Ihre benutzerdefinierte UITableViewCell-Implementierung einfügen (dank @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];
    }
}

Die @smileyborg-Antwort mit dieser Antwort zu mischen, sollte funktionieren.


42
2017-12-03 02:23



Ein wichtiger genug Gotcha lief ich nur um als Antwort zu posten.

@ smileyborgs Antwort ist größtenteils richtig. Wenn Sie jedoch einen Code in der layoutSubviews Methode Ihrer benutzerdefinierten Zellklasse, zum Beispiel die Einstellung der preferredMaxLayoutWidth, dann wird es nicht mit diesem Code ausgeführt:

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

Es verwirrte mich für eine Weile. Dann habe ich gemerkt, dass das nur daran liegt, dass layoutSubviews auf der contentView, nicht die Zelle selbst.

Mein Arbeitscode sieht so aus:

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

Beachten Sie, dass Sie, wenn Sie eine neue Zelle erstellen, ziemlich sicher sind, dass Sie nicht anrufen müssen setNeedsLayout wie es schon eingestellt sein sollte. In Fällen, in denen Sie eine Referenz auf eine Zelle speichern, sollten Sie sie wahrscheinlich aufrufen. So oder so sollte es nichts schaden.

Ein weiterer Tipp, wenn Sie Zellenunterklassen verwenden, in denen Sie Dinge wie festlegen preferredMaxLayoutWidth. Wie @smileyborg erwähnt, "wurde die Breite der Tabellenansichtzelle noch nicht auf die Tabellenansichtsbreite festgelegt". Dies gilt auch dann, wenn Sie Ihre Arbeit in Ihrer Unterklasse und nicht im View-Controller ausführen. Sie können jedoch den Zellenrahmen an dieser Stelle einfach mit der Tabellenbreite festlegen:

Zum Beispiel in der Berechnung für die Höhe:

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

(Ich speichere diese bestimmte Zelle zur Wiederverwendung, aber das ist irrelevant).


22
2018-03-05 15:53



Falls die Leute immer noch Probleme damit haben. Ich habe einen kurzen Blogbeitrag über die Verwendung von Autlayout mit UITableViews geschrieben Nutzung von Autolayout für dynamische Zellenhöhen sowie eine Open-Source-Komponente, um diese abstrakter und einfacher zu implementieren. https://github.com/Raizlabs/RZCellSizeManager


19
2018-06-27 22:49