it-swarm.com.de

Verwenden des automatischen Layouts in UITableView für dynamische Zellenlayouts und variable Zeilenhöhen

Wie verwenden Sie das automatische Layout in UITableViewCells in einer Tabellensicht, um den Inhalt und die Unteransichten der einzelnen Zellen die Zeilenhöhe (selbst/automatisch) bestimmen zu lassen und gleichzeitig die reibungslose Bildlaufleistung zu erhalten?

1406
smileyborg

TL; DR: Lesen Sie nicht gerne? Springe direkt zu den Beispielprojekten auf GitHub:

Konzeptionelle Beschreibung

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

1. Einrichten und Hinzufügen von Einschränkungen

Fügen Sie in Ihrer Unterklasse UITableViewCell Einschränkungen hinzu, sodass die Kanten der Unteransichten der Zelle an den Rändern der Zelle angeheftet sind contentView (am wichtigsten an den oberen UND unteren Rändern). ANMERKUNG: Heften Sie Unteransichten nicht an die Zelle selbst, sondern nur an die Zelle contentView! Lassen Sie die innere Inhaltsgröße dieser Unteransichten die Höhe der Zelleninhaltsansicht der Tabellenansicht bestimmen Stellen Sie sicher, dass die Einschränkungen für die Inhaltskomprimierung und für die Inhaltskomprimierung in der vertikalen Dimension für jede Unteransicht nicht von Einschränkungen mit höherer Priorität außer Kraft gesetzt werden Habe hinzugefügt. ( Huh? Hier klicken. )

Denken Sie daran, dass die Unteransichten der Zelle vertikal mit der Inhaltsansicht der Zelle verbunden sein sollen, damit sie "Druck ausüben" und die Inhaltsansicht entsprechend erweitern können. Anhand einer Beispielzelle mit einigen Unteransichten sehen Sie hier eine visuelle Darstellung dessen, was einige (nicht alle!) Ihrer Einschränkungen bedeuten würde muss so aussehen:

Example illustration of constraints on a table view cell.

Sie können sich vorstellen, dass die mehrzeilige Textbeschriftung in der obigen Beispielzelle vertikal vergrößert werden muss, um dem Text zu entsprechen. Dadurch wird die Höhe der Zelle effektiv erhöht. (Natürlich müssen Sie die Einschränkungen richtig stellen, damit dies korrekt funktioniert!)

Die richtigen Bedingungen zu schaffen, ist auf jeden Fall der schwierigste und wichtigste Teil , um dynamische Zellenhöhen mit dem automatischen Layout zu erzielen. Wenn Sie hier einen Fehler machen, kann dies dazu führen, dass alles andere nicht mehr funktioniert. Nehmen Sie sich also Zeit! Ich empfehle Ihnen, Ihre Einschränkungen in 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 in Code kann genauso einfach und wesentlich leistungsfähiger sein als Interface Builder mithilfe von Layoutankern oder einer der fantastischen Open Source-APIs, die auf GitHub verfügbar sind.

  • Wenn Sie Codeeinschränkungen hinzufügen, sollten Sie dies einmal innerhalb der Methode updateConstraints Ihrer UITableViewCell-Unterklasse tun. Beachten Sie, dass updateConstraints möglicherweise mehrmals aufgerufen wird. Um zu vermeiden, dass dieselben Einschränkungen mehrmals hinzugefügt werden, müssen Sie den Code zum Hinzufügen von Einschränkungen in updateConstraints einschließen und nach einer booleschen Eigenschaft wie didSetupConstraints suchen (die Sie nach Ihnen auf YES gesetzt haben) Führen Sie den Code zum Hinzufügen von Einschränkungen einmal aus. Wenn Sie andererseits Code haben, der vorhandene Einschränkungen aktualisiert (z. B. das Anpassen der Eigenschaft constant für einige Einschränkungen), platzieren Sie diesen in updateConstraints, jedoch außerhalb der Prüfung auf didSetupConstraints, damit er bei jedem Aufruf der Methode ausgeführt werden kann.

2. Bestimmen Sie eindeutige Kennungen für die Wiederverwendung von Tabellenansichten

Verwenden Sie für jeden eindeutigen Satz von Einschränkungen in der Zelle eine eindeutige Zellwiederverwendungskennung. 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 Zellvariante eine andere Anzahl von Unteransichten aufweist oder die Unteransichten auf unterschiedliche Weise angeordnet sind.)

Wenn Sie beispielsweise in jeder Zelle eine E-Mail-Nachricht anzeigen, haben Sie möglicherweise 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 Foto Anhang. Für jedes Layout sind ganz 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 für die Wiederverwendung aus der Warteschlange nehmen, wurden die Einschränkungen bereits hinzugefügt und können für diesen Zelltyp verwendet werden.

Beachten Sie, dass Zellen mit denselben Einschränkungen (Typen) aufgrund von Unterschieden in der eigentlichen Inhaltsgröße möglicherweise immer noch unterschiedliche Höhen haben! Verwechseln Sie nicht grundsätzlich 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 Mengen von Einschränkungen hinzu (d. H. Verwenden Sie die gleiche Wiederverwendungskennung), und versuchen Sie dann, die alten Einschränkungen zu entfernen und neue Einschränkungen nach jeder Warteschlange von Grund auf neu einzurichten. Die interne Auto-Layout-Engine ist nicht für umfangreiche Änderungen von Einschränkungen ausgelegt, und es treten massive Leistungsprobleme auf.

Für iOS 8 - Self-Sizing Cells

3. Aktivieren Sie die Zeilenhöhenschätzung

Um Zellen in der Tabellenansicht mit automatischer Größenänderung zu aktivieren, müssen Sie die rowHeight -Eigenschaft der Tabellenansicht auf UITableViewAutomaticDimension setzen. Sie müssen der estimatedRowHeight-Eigenschaft auch einen Wert zuweisen. Sobald diese beiden Eigenschaften festgelegt sind, berechnet das System anhand des automatischen Layouts die tatsächliche Höhe der Zeile

Apple: Arbeiten mit Tabellenansichtszellen mit eigener Größe

Unter iOS 8 hat Apple einen Großteil der Arbeit verinnerlicht, die Sie zuvor vor iOS 8 implementiert hatten. Damit der Zellmechanismus mit automatischer Größenanpassung funktioniert, müssen Sie zuerst die Eigenschaft rowHeight festlegen in der Tabellenansicht auf die Konstante UITableViewAutomaticDimension. Anschließend müssen Sie lediglich die Zeilenhöhenschätzung aktivieren, indem Sie die Eigenschaft estimatedRowHeight 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 erhält die Tabellenansicht einen temporären Schätz-/Platzhalter für die Zeilenhöhen von Zellen, 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 nach der Höhe, deren contentView auf der bekannten festen Breite der Inhaltsansicht (die auf der Breite der Tabellenansicht basiert, abzüglich zusätzlicher Elemente wie einem Abschnitt) basieren muss Index- 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 Anpassungen an contentSize/contentOffset der Tabellenansicht werden nach Bedarf vorgenommen).

Im Allgemeinen muss die von Ihnen angegebene Schätzung nicht sehr genau sein. Sie wird nur verwendet, um die Größe des Bildlaufindikators in der Tabellenansicht korrekt festzulegen, und in der Tabellenansicht können Sie den Bildlaufindikator auf falsche Schätzungen einstellen Bildlauf Zellen auf dem Bildschirm. Sie sollten die Eigenschaft estimatedRowHeight in der Tabellenansicht (in viewDidLoad oder ähnlich) auf einen konstanten Wert festlegen, der der "durchschnittlichen" Zeilenhöhe entspricht. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung voneinander abweichen) und Sie bemerken, dass der Scroll-Indikator beim Scrollen "springt", sollten Sie sich die Mühe machen, tableView:estimatedHeightForRowAtIndexPath: zu implementieren, um die Minimalberechnung durchzuführen erforderlich, um eine genauere Schätzung für jede Zeile zurückzugeben.

Unterstützung für iOS 7 (Implementierung der automatischen Zellgrößenanpassung selbst)

3. Führen Sie einen Layout-Durchlauf durch und ermitteln Sie die Zellenhöhe

Instanziieren Sie zunächst eine Offscreen-Instanz einer Tabellensichtzelle , 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 View-Controller gespeichert ist und nie von tableView:cellForRowAtIndexPath: zurückgegeben wird, damit die Tabellenansicht tatsächlich auf dem Bildschirm dargestellt wird.) Als Nächstes muss die Zelle mit dem genauen Inhalt (z. B. Text) konfiguriert werden , Bilder usw.), die es enthalten würde, wenn es in der Tabellenansicht angezeigt würde.

Erzwingen Sie dann, dass die Zelle ihre Unteransichten sofort gestaltet, und verwenden Sie dann die Methode systemLayoutSizeFittingSize: für die Methode UITableViewCell von contentView, um die erforderliche Höhe der Zelle zu ermitteln. Verwenden Sie UILayoutFittingCompressedSize, 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: -Delegatemethode zurückgegeben werden.

4. Verwenden Sie die geschätzten Zeilenhöhen

Wenn Ihre Tabellenansicht mehr als ein paar Dutzend Zeilen enthält, können Sie feststellen, dass das Ausführen der automatischen Layoutbeschränkungslösung beim ersten Laden der Tabellenansicht schnell den Hauptthread blockiert, da tableView:heightForRowAtIndexPath: für jede einzelne Zeile aufgerufen wird Zeile beim ersten Laden (um die Größe des Scroll-Indikators zu berechnen).

Ab iOS 7 können (und sollten) Sie die Eigenschaft estimatedRowHeight in der Tabellenansicht verwenden. Dadurch erhält die Tabellenansicht einen temporären Schätz-/Platzhalter für die Zeilenhöhen von Zellen, 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 von tableView:heightForRowAtIndexPath:) und die geschätzte Höhe mit der tatsächlichen Höhe aktualisiert.

Im Allgemeinen muss die von Ihnen angegebene Schätzung nicht sehr genau sein. Sie wird nur verwendet, um die Größe des Bildlaufindikators in der Tabellenansicht korrekt festzulegen, und in der Tabellenansicht können Sie den Bildlaufindikator auf falsche Schätzungen einstellen Bildlauf Zellen auf dem Bildschirm. Sie sollten die Eigenschaft estimatedRowHeight in der Tabellenansicht (in viewDidLoad oder ähnlich) auf einen konstanten Wert festlegen, der der "durchschnittlichen" Zeilenhöhe entspricht. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung voneinander abweichen) und Sie bemerken, dass der Scroll-Indikator beim Scrollen "springt", sollten Sie sich die Mühe machen, tableView:estimatedHeightForRowAtIndexPath: zu implementieren, um die Minimalberechnung durchzuführen erforderlich, um eine genauere Schätzung für jede Zeile zurückzugeben.

5. Fügen Sie (falls erforderlich) Zeilenhöhen-Caching hinzu

Wenn Sie alle oben genannten Schritte ausgeführt haben und beim Lösen von Einschränkungen in tableView:heightForRowAtIndexPath: immer noch feststellen, dass die Leistung inakzeptabel langsam ist, müssen Sie leider eine Zwischenspeicherung für Zellenhöhen implementieren. (Dies ist der Ansatz, den die Ingenieure von Apple vorgeschlagen haben.) Die allgemeine Idee ist, das Auto-Layout-Modul die Einschränkungen zum ersten Mal auflö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 zu einer Änderung der Zellenhöhe führen kann. Dies ist in erster Linie der Fall, wenn sich der Inhalt dieser Zelle ändert oder andere wichtige Ereignisse eintreten (z. B. die Anpassung durch den Benutzer) den Schieberegler für die dynamische Textgröße).

iOS 7 Generic Sample Code (mit vielen interessanten 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, da in UILabels Tabellenansichtszellen dynamischen Inhalt enthalten.

Xamarin (C # /. NET)

Wenn Sie Xamarin verwenden, sehen Sie sich Folgendes an: Beispielprojekt Zusammengestellt von @ KentBoogaart .

2348
smileyborg

Für IOS8 ist es ganz 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 Sie mehrere Zeilen beschriften, vergessen Sie nicht, die numberOfLines auf 0 zu setzen.

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

Der vollständige Beispielcode ist hier .

EDIT Swift 4.2 UITableViewAutomaticDimension geändert in UITableView.automaticDimension

144
William Hu

Ein schnelles Beispiel für eine variable Höhe von UITableViewCell

Aktualisiert für Swift 3

Die schnelle Antwort von William Hu ist gut, aber sie hilft mir, einige einfache, aber detaillierte Schritte zu machen, wenn ich zum ersten Mal etwas lernen lerne. Das folgende Beispiel ist mein Testprojekt, während ich lernte, eine UITableView mit variablen Zellenhöhen herzustellen. Ich habe es auf diesem grundlegenden UITableView-Beispiel für Swift basiert.

Das fertige Projekt sollte so aussehen:

 enter image description here

Erstellen Sie ein neues Projekt

Es kann sich nur um eine Single View-Anwendung handeln.

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 Auslässe für die Ansichten, die Sie Ihrer Zelle im Storyboard hinzufügen. In diesem grundlegenden Beispiel haben wir in jeder Zelle nur ein Label.

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 über den folgenden Inhalt verfügen:

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:

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

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Richten Sie das Storyboard ein

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

 enter image description here

Wichtige Notiz:

  • Das automatische Layout arbeitet mit den wichtigen zwei Codezeilen zusammen, die ich oben erwähnt habe. Wenn Sie kein automatisches Layout verwenden, funktioniert es nicht.

Andere IB-Einstellungen

Name und Bezeichner der benutzerdefinierten Klasse

Wählen Sie die Table View-Zelle aus, und legen Sie als benutzerdefinierte Klasse MyCustomCell fest (den Namen der Klasse in der hinzugefügten Swift-Datei). Legen Sie auch den Bezeichner auf cell fest (dieselbe Zeichenfolge, die wir im Code oben für die cellReuseIdentifier verwendet haben.

 enter image description here

Nullzeilen für Label

Legen Sie die Anzahl der Zeilen in Ihrem Label auf 0 fest. Dies bedeutet mehrzeilig und ermöglicht, dass sich die Größe des Etiketts basierend auf seinem Inhalt ändert.

 enter image description here

Schließen Sie die Steckdosen an

  • Ziehen Sie die Steuerung aus der Tabellenansicht im Storyboard auf die Variable tableView im Code ViewController
  • Machen Sie dasselbe für das Label in Ihrer Prototypzelle für die Variable myCellLabel in der Klasse MyCustomCell.

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 später. Wenn Sie weiterhin iOS 7 unterstützen müssen, funktioniert dies nicht für Sie.
  • Ihre eigenen benutzerdefinierten Zellen in Ihren zukünftigen Projekten haben wahrscheinlich mehr als ein Etikett. Stellen Sie sicher, dass Sie alles richtig fixiert haben, damit das automatische Layout die richtige Höhe bestimmen kann. Möglicherweise müssen Sie auch einen vertikalen Kompressionswiderstand verwenden und umarmen. Siehe diesen Artikel für mehr darüber.
  • Wenn Sie die vordere und hintere Kante (links und rechts) nicht fixieren, müssen Sie möglicherweise auch die Variable preferredMaxLayoutWidth des Labels so einstellen, dass es weiß, wann Zeilenumbrüche erfolgen sollen. Wenn Sie beispielsweise der Beschriftung im Projekt oben eine horizontale Einschränkung hinzugefügt hätten, anstatt die Vorder- und Hinterkante zu fixieren, müssten Sie diese Zeile der tableView:cellForRowAtIndexPath-Methode hinzufügen:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Siehe auch

82
Suragch

Ich habe die iOS7-Lösung von @ smileyborg in eine Kategorie eingepackt

Ich entschied mich dafür, diese clevere Lösung von @smileyborg in eine UICollectionViewCell+AutoLayoutDynamicHeightCalculation-Kategorie zu packen.

Die Kategorie korrigiert auch die in @ wildmonkeys Antwort beschriebenen Probleme (Laden einer Zelle aus einer Spitze und systemLayoutSizeFittingSize:, die CGRectZero zurückgeben)

Zwischenspeicherung wird dabei nicht berücksichtigt, entspricht aber gerade 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

Anwendungsbeispiel:

- (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 erstmal!

62
Adam Waite

Hier ist meine Lösung. Sie müssen dem TableView die geschätzte Höhe mitteilen, bevor die Ansicht geladen wird. Andernfalls kann es sich nicht wie erwartet verhalten.

Ziel c

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

Update auf Swift 4.2

override func viewWillAppear(_ animated: Bool) {
    tableView.rowHeight = UITableView.automaticDimension
    tableView.estimatedRowHeight = 65.0
}
48
m1009ct0

Die von @smileyborg vorgeschlagene Lösung ist nahezu perfekt. Wenn Sie über eine benutzerdefinierte Zelle verfügen und eine oder mehrere UILabel mit dynamischen Höhen haben möchten, gibt die systemLayoutSizeFittingSize -Methode zusammen mit der aktivierten AutoLayout-Funktion eine CGSizeZero zurück, es sei denn, Sie verschieben alle Zelleinschränkungen aus der Zelle in ihre contentView (wie von @TomSwift vorgeschlagen here Wie kann ich die Größe des Superview ändern, um alle Subviews mit Autolayout anzupassen? ).

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

Das Mischen von @smileyborg mit dieser Antwort sollte funktionieren.

44
wildmonkey

Eine wichtige genug Gotcha, auf die ich gerade als Antwort gestoßen bin.

@ smileyborgs Antwort ist meistens richtig. Wenn Sie jedoch Code in der layoutSubviews-Methode Ihrer benutzerdefinierten Zellklasse haben, zum Beispiel die preferredMaxLayoutWidth-Einstellung, wird er nicht mit diesem Code ausgeführt:

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

Es hat mich für eine Weile verwirrt. Dann wurde mir klar, dass dies darauf zurückzuführen ist, dass diese nur LayoutSubviews auf der contentView auslösen, nicht auf der 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;

Wenn Sie eine neue Zelle erstellen, bin ich mir ziemlich sicher, dass Sie setNeedsLayout nicht aufrufen müssen, da sie bereits festgelegt sein sollte. Wenn Sie einen Verweis auf eine Zelle speichern, sollten Sie ihn wahrscheinlich aufrufen. So oder so sollte es nichts schaden.

Ein weiterer Tipp, wenn Sie Zellunterklassen verwenden, in denen Sie Dinge wie preferredMaxLayoutWidth einstellen. Wie @smileyborg erwähnt, "wurde die Breite Ihrer Tabellenansicht-Zelle noch nicht auf die Breite der Tabellenansicht festgelegt". Dies ist wahr und ein Problem, wenn Sie Ihre Arbeit in Ihrer Unterklasse und nicht im View-Controller erledigen. Sie können jedoch den Zellenrahmen an dieser Stelle einfach mit der Tabellenbreite festlegen:

Zum Beispiel bei der Berechnung der 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 füge diese bestimmte Zelle für die Wiederverwendung in den Cache, aber das ist irrelevant).

24
Bob Spryn

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

21
Alex Rouse

(für Xcode 8.x/Xcode 9.x unten gelesen)

Beachten Sie das folgende Problem in Xcode 7.x, das zu Verwirrung führen kann:

Interface Builder behandelt die Einrichtung der Zellen für die automatische Größenanpassung nicht ordnungsgemäß. Auch wenn Ihre Einschränkungen absolut gültig sind, klagt IB und gibt Ihnen verwirrende Vorschläge und Fehler. Der Grund ist, dass IB nicht willens ist, die Höhe der Zeile zu ändern, wenn Ihre Einschränkungen dies erfordern (damit die Zelle Ihren Inhalt einfügt). Stattdessen bleibt die Höhe der Zeile unverändert und es wird vorgeschlagen, die Einschränkungen zu ändern, die sollten Sie ignorieren.

Stellen Sie sich zum Beispiel vor, Sie haben alles richtig eingerichtet, keine Warnungen, keine Fehler, alles funktioniert.

 enter image description here

Wenn Sie jetzt die Schriftgröße ändern (in diesem Beispiel ändere ich die Schriftgröße der Beschriftungsbeschriftung von 17.0 auf 18.0).

 enter image description here

Da die Schriftgröße zunimmt, möchte das Etikett nun 3 Zeilen einnehmen (davor hatte es 2 Zeilen).

Wenn der Interface Builder wie erwartet funktioniert, wird die Höhe der Zelle angepasst, um die neue Etikettenhöhe zu berücksichtigen. Tatsächlich wird jedoch angezeigt, dass IB das rote Symbol für das automatische Layoutfehler anzeigt und vorschlägt, dass Sie die Prioritäten für das Anpassen/Komprimieren ändern.

 enter image description here

Sie sollten diese Warnungen ignorieren. Sie können stattdessen die Höhe der Zeile manuell ändern (wählen Sie Zelle> Größeninspektor> Zeilenhöhe).

 enter image description here

Ich habe diese Höhe mit jedem Klick (mit dem Aufwärts-/Abwärtsschritt) geändert, bis die roten Pfeile verschwinden! (Sie werden tatsächlich gelbe Warnungen erhalten. An diesem Punkt machen Sie einfach "Aktualisierungsframes", es sollte alles funktionieren).

* Beachten Sie, dass Sie diese roten Fehler oder gelben Warnungen im Interface Builder nicht wirklich beheben müssen. Zur Laufzeit wird alles korrekt funktionieren (auch wenn IB Fehler/Warnungen anzeigt). Stellen Sie nur sicher, dass Sie zur Laufzeit im Konsolenprotokoll keine AutoLayout-Fehler erhalten.

Tatsächlich ist der Versuch, die Zeilenhöhe in IB immer zu aktualisieren, sehr ärgerlich und manchmal (wegen gebrochener Werte) nahezu unmöglich.

Um ärgerliche IB-Warnungen/Fehler zu vermeiden, können Sie die betroffenen Ansichten auswählen und in Size Inspector für die Eigenschaft AmbiguityVerify Position Only wählen.

 enter image description here


Xcode 8.x/Xcode 9.x scheint (manchmal) etwas anderes als Xcode 7.x zu machen, aber immer noch falsch. Selbst wenn beispielsweise compression resistance priority/hugging priority auf required (1000) eingestellt ist, kann der Interface Builder eine Beschriftung strecken oder ausschneiden, um sie an die Zelle anzupassen (anstatt die Zellenhöhe an die Beschriftung anzupassen). In diesem Fall werden möglicherweise auch keine AutoLayout-Warnungen oder Fehler angezeigt. Oder manchmal macht es genau das, was Xcode 7.x getan hat, wie oben beschrieben.

19

Stellen Sie zum Festlegen der automatischen Bemaßung für die Zeilenhöhe und die geschätzte Zeilenhöhe die folgenden Schritte ein, um die automatische Bemaßung für das Layout der Zellen-/Zeilenhöhe wirksam zu machen. 

  • Weisen Sie TableView-Datenquellen und Delegaten zu und implementieren Sie sie
  • Weisen Sie UITableViewAutomaticDimension rowHeight & geschätzteRowHeight zu
  • Implementieren Sie Delegate/dataSource-Methoden (d. H. heightForRowAt und geben Sie einen Wert UITableViewAutomaticDimension zurück).

-

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

Swift:

@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
}

Für die Labelinstanz in UITableviewCell

  • Setze Zeilenanzahl = 0 (& Zeilenumbruchmodus = Ende abschneiden)
  • Legen Sie alle Einschränkungen (oben, unten, rechts links) in Bezug auf den Superview-/Zellencontainer fest.
  • Optional: Legen Sie die Mindesthöhe für die Beschriftung fest, wenn Sie möchten, dass der vertikale Mindestbereich durch die Beschriftung abgedeckt wird, auch wenn keine Daten vorhanden sind.

enter image description here

Hinweis : Wenn Sie mehr als ein Label (UIElements) mit dynamischer Länge haben, das entsprechend seiner Inhaltsgröße angepasst werden sollte: Passen Sie 'Content Hugging und Compression Resistance Priority' für Labels an, mit denen Sie expandieren/komprimieren möchten Höhere Priorität.

18
Krunal

Solange Ihr Layout in Ihrer Zelle gut ist.

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

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

Update: Sie sollten die in iOS 8 eingeführte dynamische Größenänderung verwenden.

18

Wie @ Bob-Spryn Ich bin auf ein wichtiges Thema gestoßen, dass ich dies als Antwort poste.

Ich kämpfte eine Zeit lang mit @ smileyborgs answer. Ich stieß darauf, dass, wenn Sie Ihre Prototypzelle in IB mit zusätzlichen Elementen (UILabels, UIButtons usw.) in IB definiert haben, wenn Sie die Zelle mit [[YourTableViewCellClass alloc] init] instanziieren, es nicht alle anderen Elemente in dieser Zelle instanziiert, es sei denn Sie haben Code geschrieben, um das zu tun. (Ich hatte eine ähnliche Erfahrung mit initWithStyle.)

Damit das Storyboard alle zusätzlichen Elemente instanziiert, erhalten Sie Ihre Zelle mit [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (Nicht [tableView dequeueReusableCellWithIdentifier:forIndexPath:], da dies interessante Probleme verursachen wird.) Wenn Sie dies tun, werden alle in IB definierten Elemente instanziiert.

16
nickb

Dynamische Tabellenseitenhöhe und automatisches Layout

Eine gute Möglichkeit, das Problem mit Storyboard Auto Layout zu lösen:

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

Eine weitere "Lösung": Überspringen Sie diese Frustration und verwenden Sie stattdessen UIScrollView, um ein Ergebnis zu erhalten, das identisch mit UITableView aussieht und sich anfühlt.

Das war die schmerzhafte "Lösung" für mich, nachdem ich buchstäblich mehr als 20 sehr frustrierende Stunden damit verbracht hatte, etwas zu bauen, was Smiley Smorg vorschlug, und über viele Monate hinweg versagte und drei Versionen der App Store-Versionen.

Wenn Sie wirklich die Unterstützung für iOS 7 benötigen (für uns ist das unerlässlich), dann ist die Technologie einfach zu spröde und Sie werden versuchen, die Haare auszuprobieren. Und diese UITableView ist in der Regel ein Overkill, wenn Sie nicht einige der erweiterten Zeilenbearbeitungsfunktionen verwenden und/oder 1000 "Zeilen" wirklich unterstützen müssen (in unserer App sind es realistischerweise nie mehr als 20 Zeilen). 

Der zusätzliche Bonus ist, dass der Code wahnsinnig einfach wird, verglichen mit dem gesamten Delegate-Mist und hin und her, der mit UITableView geliefert wird. Es ist nur eine einzige Code-Schleife in viewOnLoad, die elegant aussieht und einfach zu verwalten ist.

Hier sind einige Tipps, wie das geht:

1) Erstellen Sie mit Hilfe des Storyboards oder einer Nib-Datei einen ViewController und die zugehörige Stammansicht.

2) Ziehen Sie eine UIScrollView in Ihre Root-Ansicht.

3) Fügen Sie der oberen Ansicht die oberen, unteren, linken und rechten Randbedingungen hinzu, damit die UIScrollView die gesamte Stammansicht füllt.

4) Fügen Sie eine UIView in die UIScrollView ein und nennen Sie sie "Container". Fügen Sie der UIScrollView (ihrem übergeordneten Element) obere, untere, linke und rechte Einschränkungen hinzu. KEY TRICK: Fügen Sie außerdem eine Einschränkung "Gleiche Breiten" hinzu, um UIScrollView und UIView zu verknüpfen.

Sie erhalten einen Fehler "Die Bildlaufansicht hat mehrdeutige durchlaufbare Inhaltshöhe" und Ihr Container-UIView sollte eine Höhe von 0 Pixel haben. Keiner der Fehler scheint zu sein, wenn die App ausgeführt wird.

5) Erstellen Sie für jede Ihrer "Zellen" NIB-Dateien und -Controller. Verwenden Sie UIView nicht UITableViewCell.

5) In Ihrem Stamm-ViewController fügen Sie im Wesentlichen alle "Zeilen" zum UIView-Container hinzu und fügen programmgesteuert Einschränkungen ein, die den linken und den rechten Rand mit der Containeransicht verbinden, deren Oberkanten entweder mit der Containeransicht oben (für das erste Element) oder die vorherige Zelle. Verbinden Sie dann die letzte Zelle mit dem Behälterboden.

Bei uns befindet sich jede "Zeile" in einer Nib-Datei. Der Code sieht also ungefähr so ​​aus:

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!)
    }
}

Und hier ist der Code für 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))
    }
}

Das einzige "Gotcha", das ich bisher mit diesem Ansatz gefunden habe, ist, dass UITableView beim Scrollen eine nette Funktion der "schwebenden" Abschnittsheader oben in der Ansicht hat. Die obige Lösung tut dies nicht, wenn Sie nicht mehr Programmieren hinzufügen, aber in unserem speziellen Fall war diese Funktion nicht zu 100% unerlässlich und niemand merkte, wann sie wegging.

Wenn Sie Teiler zwischen Ihren Zellen wünschen, fügen Sie einfach eine UIView mit einer Pixelhöhe unterhalb Ihrer benutzerdefinierten "Zelle" hinzu, die wie ein Teiler aussieht.

Stellen Sie sicher, dass "Bounces" und "Bounce Vertical" aktiviert sind, damit die Aktualisierungssteuerung funktioniert und es eher wie eine Tableview wirkt.

TableView zeigt einige leere Zeilen und Trennlinien unter Ihrem Inhalt an, wenn dies nicht den gesamten Bildschirm ausfüllt, da diese Lösung dies nicht tut. Ich persönlich bevorzuge es jedoch, wenn diese leeren Reihen ohnehin nicht vorhanden wären - bei variabler Zellenhöhe sah es für mich sowieso immer "fehlerhaft" aus, die leeren Reihen dort zu haben.

Ich hoffe, dass ein anderer Programmierer mein Posting liest, BEVOR Sie 20 Stunden damit verschwenden, es mit Table View in seiner eigenen App herauszufinden. :)

13
alpsystems.com

Ich musste dynamische Ansichten (Setup-Ansichten und Abhängigkeiten nach Code) verwenden, und als ich die Breite des Bezeichnungsfelds "PreferredMaxLayoutWidth" festlegen wollte, war die Breite 0. Daher habe ich eine falsche Zellenhöhe.

Dann habe ich hinzugefügt 

[cell layoutSubviews];

vor der Ausführung

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

Danach war die Breite des Etiketts erwartungsgemäß und die dynamische Höhe berechnete sich als richtig.

11
Mansurov Ruslan
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

 enter image description here

11
Abo3atef

Nehmen wir an, Sie haben eine Zelle mit einer Unteransicht und möchten, dass die Höhe der Zelle hoch genug ist, um die Unteransicht + Auffüllung zu umfassen.

1) Setzen Sie die untere Einschränkung der Unteransicht auf die cell.contentView minus der gewünschten Auffüllung. Legen Sie keine Einschränkungen für die Zelle oder cell.contentView selbst fest.

2) Setzen Sie entweder die rowHeight-Eigenschaft von tableView oder tableView:heightForRowAtIndexPath: auf UITableViewAutomaticDimension.

3) Setzen Sie entweder die estimatedRowHeight-Eigenschaft von tableView oder tableView:estimatedHeightForRowAtIndexPath: auf eine optimale Schätzung der Höhe.

Das ist es.

8
Vadoff

Wenn Sie das Layout programmgesteuert erstellen, sollten Sie Folgendes unter iOS 10 mit Ankern in Swift beachten.

Es gibt drei Regeln/Schritte

NUMMER 1: Legen Sie diese beiden Eigenschaften von tableview für viewDidLoad fest. Die erste sagt der tableview, dass dynamische Größen in ihren Zellen erwartet werden. Die zweite ist, die App nur die Größe der Bildlaufleistenanzeige berechnen zu lassen Performance.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMMER 2: Es ist wichtig, dass Sie die Unteransichten zur Inhaltsansicht der Zelle hinzufügen, nicht zur Ansicht. Außerdem müssen Sie die Unteransichten mit ihrem layoutsmarginguide oben und unten verankern. Dies ist ein funktionierendes Beispiel für die Vorgehensweise.

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)

        ])
}

Erstellen Sie eine Methode, die die Unteransichten hinzufügt und das Layout ausführt. Rufen Sie es in der init-Methode auf. 

Nummer 3: Rufen Sie die Methode nicht an:

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

Wenn Sie dies tun, überschreiben Sie Ihre Implementierung.

Befolgen Sie diese 3 Regeln für dynamische Zellen in Tabellenansichten.

hier ist eine funktionierende Implementierung https://github.com/jamesrochabrun/MinimalViewController

4
James Rochabrun

Wenn Sie eine long Zeichenfolge haben. z.B. eine, die nicht einen Zeilenumbruch hat. Dann könnten Sie auf einige Probleme stoßen.

Das Update wird durch die akzeptierte Antwort und einige andere Antworten erwähnt. Sie müssen nur hinzufügen

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Ich finde die Antwort von Suragh die vollständigste und prägnanteste, daher nicht verwirrend. 

Nicht erklären warum. Lass uns das tun. 

Legen Sie den folgenden Code in einem Projekt ab. 

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

Beachten Sie, dass ich keine size - Einschränkungen hinzugefügt habe. Ich habe nur centerX, centerY-Einschränkungen hinzugefügt. Aber trotzdem wird das Etikett richtig dimensioniert. Warum? 

Wegen contentSize.

Behalten Sie zunächst Schritt0 und kommentieren Sie die Schritte 1-6 aus. Lass setupLayout() bleiben. Beobachten Sie das Verhalten. 

Dann kommentieren Sie Schritt1 und beobachten Sie.

Dann kommentieren Sie Schritt2 und beobachten Sie. 

Tun Sie dies, bis Sie alle 6 Schritte auskommentiert und deren Verhalten beobachtet haben. 

Was kann daraus schließen? Welche Faktoren können contenSize verändern?

  1. Textlänge: Wenn Sie einen längeren Text haben, erhöht sich width von intrinsicContentSize
  2. Zeilenumbrüche: Wenn Sie \n hinzufügen, entspricht die Breite des intrinsicContentSize der maximalen Breite aller Zeilen. Wenn eine Zeile 25 Zeichen, eine andere 2 Zeichen und eine andere 21 Zeichen enthält, wird Ihre Breite anhand der 25 Zeichen berechnet
  3. Anzahl der erlaubten Zeilen: Sie müssen numberOfLines auf 0 setzen, da Sie sonst keine mehreren Zeilen haben. Ihr numberOfLines passt die height Ihrer intrinsicContentSize an.
  4. Durchführen von Anpassungen: Stellen Sie sich vor, dass die Breite Ihres intrinsicContentSize basierend auf Ihrem Text 200 und die Höhe 100 war, Sie wollten jedoch die Breite auf den Container des Labels beschränken. Die Lösung ist, es auf eine gewünschte Breite einzustellen. Sie können dies tun, indem Sie preferredMaxLayoutWidth auf 130 setzen. Dann wird Ihre neue intrinsicContentSize eine Breite von ungefähr 130 haben. Die Höhe wäre natürlich mehr als 100, da Sie mehr Zeilen benötigen. Wenn Ihre Einschränkungen richtig eingestellt sind, brauchen Sie dies nicht zu verwenden. Mehr dazu unter dieser Antwort und ihren Kommentaren. Sie müssen preferredMaxLayoutWidth nur verwenden, wenn die Breite/Höhe nicht durch Einschränkungen eingeschränkt ist. In einem Beispiel heißt es: "Text nicht umbrechen, es sei denn, er überschreitet preferredMaxLayoutWidth". Aber mit 100% iger Sicherheit, wenn Sie Leading/Trailing und numberOfLines auf 0 setzen, sind Sie gut! Lange Geschichte kurz die meisten Antworten hier, die die Verwendung empfehlen, sind FALSCH! Sie brauchen es nicht. Wenn Sie es brauchen, ist dies ein Zeichen dafür, dass Ihre Einschränkungen nicht richtig eingestellt sind oder dass Sie einfach keine Einschränkungen haben.

  5. Schriftgröße: Beachten Sie außerdem, dass bei Erhöhung der Schriftgröße die height von intrinsicContentSize zunimmt. Ich habe das nicht in meinem Code gezeigt. Sie können das selbst ausprobieren.

Zurück zu deinem tableViewCell-Beispiel: 

Alles was Sie tun müssen, ist:

  • setze numberOfLines auf 0
  • beschriften Sie die Beschriftung korrekt auf die Ränder/Kanten
  • Es ist nicht notwendig, preferredMaxLayoutWidth einzustellen.
2
Honey

In meinem Fall waren die Auffüllungen auf die SectionHeader- und SectionFooter-Höhen zurückzuführen, bei denen das Storyboard es mir erlaubte, sie auf Minimum 1 zu ändern. Also in viewDidLoad-Methode:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
1
Rashid

Ich habe nur ein paar blöde Versuche gemacht, mit den zwei Werten von rowHeight und estimatedRowHeight etwas auszusetzen und dachte nur daran, dass es einige Einblicke in das Debugging geben könnte:

Wenn Sie beide OR setzen, wird nur die estimatedRowHeight gesetzt, und Sie erhalten das gewünschte Verhalten:

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

Es wird empfohlen, dass Sie Ihr Bestes geben, um die korrekte Schätzung zu erhalten, das Endergebnis ist jedoch nicht unterschiedlich. Es wird nur Ihre Leistung beeinflussen.

 enter image description here


Wenn Sie nur die rowHeight setzen, müssen Sie nur Folgendes tun:

tableView.rowHeight = UITableViewAutomaticDimension

ihr Endergebnis wäre nicht wie gewünscht:

 enter image description here


Wenn Sie die estimatedRowHeight auf 1 oder kleiner setzen, werden Sie crash unabhängig von der rowHeight

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

Ich bin mit der folgenden Fehlermeldung abgestürzt:

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
1
Honey

In meinem Fall muss ich eine benutzerdefinierte Zelle mit einem Bild erstellen, das vom Server kommt und von beliebiger Breite und Höhe sein kann. Und zwei UILabels mit dynamischer Größe (sowohl Breite als auch Höhe)

ich habe das gleiche hier in meiner Antwort mit Autolayout und programmgesteuert erreicht:

Grundsätzlich hat die @smileyBorg-Antwort geholfen, aber SystemLayoutSizeFittingSize hat bei mir nie funktioniert. In meinem Ansatz:

1. Keine Verwendung der Eigenschaft zur automatischen Berechnung der Zeilenhöhe. 2.Keine Verwendung der geschätzten Höhe. 3. Keine unnötige Aktualisierung erforderlich. 4. Keine Verwendung der automatischen bevorzugten maximalen Layoutbreite. 5. Keine Verwendung von systemLayoutSizeFittingSize.) (sollte Verwendung haben, aber nicht für mich arbeiten, ich weiß nicht, was es intern tut), sondern stattdessen meine Methode - (Float) getViewHeight funktioniert und ich weiß, was es intern tut.

Ist es möglich, unterschiedliche Höhen in einer UITableView-Zelle zu haben, wenn ich verschiedene Arten der Anzeige der Zelle verwende?

1
Alok

In Bezug auf die akzeptierte Antwort von @smileyborg habe ich gefunden

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

in einigen Fällen unzuverlässig sein, wenn Einschränkungen mehrdeutig sind. Es ist besser, die Layout-Engine zur Berechnung der Höhe in eine Richtung zu zwingen, indem Sie die Hilfekategorie in UIView verwenden:

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

Wobei w: die Breite der Tabellenansicht ist

1
railwayparade

Schnell 4

@IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var tableView: UITableView!

Überschreiben Sie func viewDidLoad () { super.viewDidLoad ()

    self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
}

// Beobachter hinzugefügt, um die Höhe der Tabellenansicht basierend auf dem Inhalt anzupassen

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

// Beobachter entfernen deinit {

    NotificationCenter.default.removeObserver(self)

}
0
Manee ios

Schnell 4.2

Dies kann dazu beitragen, dass Sie den Code in der App mehrmals schreiben müssen.

Ich habe die folgende Erweiterung zu UITableView hinzugefügt: 

extension UITableView {

    func setDynamicRowHeight(withEstimatedHeight height : CGFloat){
        self.estimatedRowHeight = height
        self.rowHeight = UITableViewAutomaticDimension
    }
}

in jeder Klasse muss ich die Zeilenhöhe von tableView auf dynamisch setzen

Ich rufe die obige Funktion nur im ViewDidLoad () - Aufruf auf, der so einfach ist:

  override func viewDidLoad() {
    super.viewDidLoad()
    // enter your estimated row height. 
    self.tableView.setDynamicRowHeight(withEstimatedHeight: 88.0)

}
0
MhmdRizk

Fügen Sie einfach diese beiden Funktionen in Ihren Viewcontroller ein, um Ihr Problem zu lösen. Liste ist hier ein String-Array, das Ihren String aus jeder Zeile enthält.

 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
}
0
Parth Barot