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


1501

Wie verwenden Sie das automatische Layout in UITableViewCells in einer Tabellenansicht, damit der Inhalt und die Unteransichten jeder Zelle die Zeilenhöhe (selbst / automatisch) bestimmen und gleichzeitig eine reibungslose Bildlaufleistung gewährleisten?


Hier ist der Beispielcode in Swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

Um die meisten Antworten in Bezug auf ein mehrzeiliges UILabel zu verstehen, müssen Sie genau wissen, wie contentSizeund wie preferredMaxLayoutWidth. Siehe hier . Wenn Sie jedoch Ihre Einschränkungen korrekt einrichten, sollten Sie diese nicht benötigen preferredMaxLayoutWidthund können in der Tat zu unerwarteten Ergebnissen führen.
Honig

Antworten:


2387

TL; DR: Lesen Sie nicht gern? Wechseln Sie direkt zu den Beispielprojekten auf GitHub:

Konzeptionelle Beschreibung

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

1. Einrichten und Hinzufügen von Einschränkungen

UITableViewCellFügen Sie in Ihrer Unterklasse Einschränkungen hinzu, sodass die Kanten der Unteransichten der Zelle an den Rändern der Inhaltsansicht der Zelle befestigt sind (am wichtigsten an den oberen UND unteren Rändern). HINWEIS: Pin-Unteransichten nicht an die Zelle selbst anheften. nur zu den Zellen contentView! Lassen Sie die intrinsische Inhaltsgröße dieser Unteransichten die Höhe der Inhaltsansicht der Tabellenansichtszelle bestimmen, indem Sie sicherstellen, dass der Inhaltskomprimierungswiderstand und die inhaltlichen Einschränkungen in der vertikalen Dimension für jede Unteransicht nicht durch von Ihnen hinzugefügte Einschränkungen mit höherer Priorität überschrieben werden. ( Huh? Hier klicken. )

Denken Sie daran, dass die Unteransichten der Zelle vertikal mit der Inhaltsansicht der Zelle verbunden werden sollen, damit sie "Druck ausüben" und die Inhaltsansicht entsprechend erweitern können. Anhand einer Beispielzelle mit einigen Unteransichten sehen Sie hier visuell, wie einige (nicht alle!) Ihrer Einschränkungen aussehen müssten:

Beispieldarstellung von Einschränkungen für eine Tabellenansichtszelle.

Sie können sich vorstellen, dass beim Hinzufügen von mehr Text zur mehrzeiligen Körperbezeichnung in der obigen Beispielzelle diese vertikal wachsen muss, um dem Text zu entsprechen, wodurch die Zelle effektiv an Höhe zunimmt. (Natürlich müssen Sie die Einschränkungen richtig einstellen, damit dies richtig funktioniert!)

Die richtigen Einschränkungen sind definitiv der schwierigste und wichtigste Teil , um dynamische Zellenhöhen mit Auto Layout zu erreichen. Wenn Sie hier einen Fehler machen, kann dies dazu führen, dass alles andere nicht mehr funktioniert - nehmen Sie sich also Zeit! Ich empfehle, Ihre Einschränkungen im Code einzurichten, da Sie genau wissen, welche Einschränkungen wo hinzugefügt werden, und es viel einfacher ist, Fehler zu beheben, wenn etwas schief geht. Das Hinzufügen von Einschränkungen im Code kann genauso einfach und wesentlich leistungsfähiger sein als Interface Builder mit Layout-Ankern oder einer der fantastischen Open Source-APIs, die auf GitHub verfügbar sind.

  • Wenn Sie dem Code Einschränkungen hinzufügen, sollten Sie dies einmal in der updateConstraintsMethode Ihrer UITableViewCell-Unterklasse tun. Beachten Sie, dass dies updateConstraintsmöglicherweise mehrmals aufgerufen wird. Um zu vermeiden, dass dieselben Einschränkungen mehrmals hinzugefügt werden, sollten Sie Ihren Code zum Hinzufügen von Einschränkungen updateConstraintsin eine Prüfung auf eine boolesche Eigenschaft wie didSetupConstraints(die Sie nach dem Ausführen Ihrer Einschränkung auf YES setzen) einschließen - Code einmal hinzufügen). Wenn Sie jedoch Code haben, der vorhandene Einschränkungen aktualisiert (z. B. das Anpassen der constantEigenschaft für einige Einschränkungen), platzieren Sie diesen in, updateConstraintsaber außerhalb der Prüfung, didSetupConstraintsdamit er bei jedem Aufruf der Methode ausgeführt werden kann.

2. Bestimmen Sie eindeutige Kennungen für die Wiederverwendung von Zellen in der Tabellenansicht

Verwenden Sie für jeden eindeutigen Satz von Einschränkungen in der Zelle eine eindeutige Kennung für die Wiederverwendung von Zellen. Mit anderen Worten, wenn Ihre Zellen mehr als ein eindeutiges Layout haben, sollte jedes eindeutige Layout eine eigene Wiederverwendungskennung erhalten. (Ein guter Hinweis, dass Sie eine neue Wiederverwendungskennung verwenden müssen, ist, wenn Ihre Zellenvariante eine andere Anzahl von Unteransichten aufweist oder die Unteransichten unterschiedlich angeordnet sind.)

Wenn Sie beispielsweise in jeder Zelle eine E-Mail-Nachricht anzeigen, haben Sie möglicherweise 4 eindeutige Layouts: Nachrichten mit nur einem Betreff, Nachrichten mit einem Betreff und einem Textkörper, Nachrichten mit einem Betreff und einem Fotoanhang sowie Nachrichten mit einem Betreff. Körper und Fotoaufsatz. Für jedes Layout sind völlig andere Einschränkungen erforderlich. Sobald die Zelle initialisiert und die Einschränkungen für einen dieser Zelltypen hinzugefügt wurden, sollte die Zelle eine eindeutige Wiederverwendungskennung erhalten, die für diesen Zelltyp spezifisch ist. Das heißt, wenn Sie eine Zelle zur Wiederverwendung aus der Warteschlange entfernen, wurden die Einschränkungen bereits hinzugefügt und können für diesen Zelltyp verwendet werden.

Beachten Sie, dass Zellen mit denselben Einschränkungen (Typ) aufgrund der unterschiedlichen Größe des Inhalts möglicherweise immer noch unterschiedliche Höhen haben! Verwechseln Sie nicht grundlegend unterschiedliche Layouts (unterschiedliche Einschränkungen) mit unterschiedlichen berechneten Ansichtsrahmen (gelöst aus identischen Einschränkungen) aufgrund unterschiedlicher Inhaltsgrößen.

  • Fügen Sie dem gleichen Wiederverwendungspool keine Zellen mit völlig unterschiedlichen Einschränkungen hinzu (dh verwenden Sie dieselbe Wiederverwendungskennung), und versuchen Sie dann, die alten Einschränkungen zu entfernen und nach jeder Warteschlange neue Einschränkungen von Grund auf neu einzurichten. Die interne Auto-Layout-Engine ist nicht für große Änderungen von Einschränkungen ausgelegt, und es treten massive Leistungsprobleme auf.

Für iOS 8 - Zellen mit Selbstgröße

3. Aktivieren Sie die Zeilenhöhenschätzung

Um Zellen der Tabellengröße mit Selbstgröße zu aktivieren, müssen Sie die rowHeight-Eigenschaft der Tabellenansicht auf UITableViewAutomaticDimension festlegen. Sie müssen der geschätztenRowHeight-Eigenschaft auch einen Wert zuweisen. Sobald diese beiden Eigenschaften festgelegt sind, berechnet das System mithilfe des automatischen Layouts die tatsächliche Höhe der Zeile

Apple: Arbeiten mit Zellen der Tabellengröße mit Selbstgröße

Mit iOS 8 hat Apple einen Großteil der Arbeit verinnerlicht, die zuvor von Ihnen vor iOS 8 implementiert werden musste. Damit der Mechanismus für Zellen mit Selbstgröße funktioniert, müssen Sie zuerst die rowHeightEigenschaft in der Tabellenansicht auf die Konstante setzen UITableViewAutomaticDimension. Anschließend müssen Sie lediglich die Zeilenhöhenschätzung aktivieren, indem Sie die estimatedRowHeightEigenschaft der Tabellenansicht auf einen Wert ungleich Null setzen. Beispiel:

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

Dadurch wird der Tabellenansicht eine temporäre Schätzung / ein Platzhalter für die Zeilenhöhen von Zellen bereitgestellt, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen auf dem Bildschirm gescrollt werden, wird die tatsächliche Zeilenhöhe berechnet. Um die tatsächliche Höhe für jede Zeile zu bestimmen, fragt die Tabellenansicht jede Zelle automatisch, welche Höhe sie contentViewbenötigt, basierend auf der bekannten festen Breite der Inhaltsansicht (die auf der Breite der Tabellenansicht basiert, abzüglich zusätzlicher Elemente wie eines Abschnittsindex) oder Zubehöransicht) und die Einschränkungen für das automatische Layout, die Sie der Inhaltsansicht und den Unteransichten der Zelle hinzugefügt haben. Sobald diese tatsächliche Zellenhöhe ermittelt wurde, wird die alte geschätzte Höhe für die Zeile mit der neuen tatsächlichen Höhe aktualisiert (und alle Anpassungen an contentSize / contentOffset der Tabellenansicht werden nach Bedarf für Sie vorgenommen).

Im Allgemeinen muss die von Ihnen angegebene Schätzung nicht sehr genau sein. Sie wird nur verwendet, um die Größe der Bildlaufanzeige in der Tabellenansicht korrekt zu bestimmen, und die Tabellenansicht passt die Bildlaufanzeige gut an, wenn Sie falsche Schätzungen vornehmen Bildlaufzellen auf dem Bildschirm. Sie sollten die estimatedRowHeightEigenschaft in der Tabellenansicht (in viewDidLoadoder ähnlich) auf einen konstanten Wert setzen, der der "durchschnittlichen" Zeilenhöhe entspricht. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung unterschiedlich sind) und Sie beim Scrollen die Bildlaufanzeige "springen" bemerken, sollten Sie sich die Mühe tableView:estimatedHeightForRowAtIndexPath:machen, die minimale Berechnung durchzuführen , die erforderlich ist, um eine genauere Schätzung für jede Zeile zurückzugeben.

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

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

Instanziieren Sie zunächst eine Offscreen-Instanz einer Tabellenansichtszelle, eine Instanz für jede Wiederverwendungskennung , die ausschließlich für Höhenberechnungen verwendet wird. (Offscreen bedeutet, dass die Zellreferenz in einer Eigenschaft / ivar auf dem Ansichtscontroller gespeichert und nie zurückgegeben wird, tableView:cellForRowAtIndexPath:damit die Tabellenansicht tatsächlich auf dem Bildschirm gerendert wird.) Als Nächstes muss die Zelle mit dem genauen Inhalt (z. B. Text, Bilder usw.) konfiguriert werden. dass es gelten würde, wenn es in der Tabellenansicht angezeigt würde.

Erzwingen Sie dann, dass die Zelle ihre Unteransichten sofort anordnet, und verwenden Sie dann die systemLayoutSizeFittingSize:Methode auf den UITableViewCell's contentView, um herauszufinden, wie hoch die erforderliche Höhe der Zelle ist. Verwenden Sie UILayoutFittingCompressedSizediese Option , um die kleinste Größe zu erhalten, die für den gesamten Inhalt der Zelle erforderlich ist. Die Höhe kann dann von der tableView:heightForRowAtIndexPath:Delegatmethode zurückgegeben werden.

4. Verwenden Sie Geschätzte Zeilenhöhen

Wenn Ihre Tabellenansicht mehr als ein paar Dutzend Zeilen enthält, werden Sie feststellen, dass das Lösen von Auto-Layout-Einschränkungen beim ersten Laden der Tabellenansicht den Hauptthread schnell blockieren kann, wie dies tableView:heightForRowAtIndexPath:für jede einzelne Zeile beim ersten Laden aufgerufen wird ( um die Größe der Bildlaufanzeige zu berechnen).

Ab iOS 7 können (und sollten) Sie die estimatedRowHeightEigenschaft in der Tabellenansicht verwenden. Dadurch wird der Tabellenansicht eine temporäre Schätzung / ein Platzhalter für die Zeilenhöhen von Zellen bereitgestellt, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen auf dem Bildschirm gescrollt werden, wird die tatsächliche Zeilenhöhe berechnet (durch Aufrufen tableView:heightForRowAtIndexPath:) und die geschätzte Höhe mit der tatsächlichen aktualisiert.

Im Allgemeinen muss die von Ihnen angegebene Schätzung nicht sehr genau sein. Sie wird nur verwendet, um die Größe der Bildlaufanzeige in der Tabellenansicht korrekt zu bestimmen, und die Tabellenansicht passt die Bildlaufanzeige gut an, wenn Sie falsche Schätzungen vornehmen Bildlaufzellen auf dem Bildschirm. Sie sollten die estimatedRowHeightEigenschaft in der Tabellenansicht (in viewDidLoadoder ähnlich) auf einen konstanten Wert setzen, der der "durchschnittlichen" Zeilenhöhe entspricht. Nur wenn Ihre Zeilenhöhen extrem variabel sind (z. B. um eine Größenordnung unterschiedlich sind) und Sie beim Scrollen die Bildlaufanzeige "springen" bemerken, sollten Sie sich die Mühe tableView:estimatedHeightForRowAtIndexPath:machen, die minimale Berechnung durchzuführen , die erforderlich ist, um eine genauere Schätzung für jede Zeile zurückzugeben.

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

Wenn Sie alle oben genannten Schritte ausgeführt haben und immer noch feststellen, dass die Leistung beim Lösen von Einschränkungen inakzeptabel langsam ist tableView:heightForRowAtIndexPath:, müssen Sie leider ein gewisses Caching für Zellenhöhen implementieren. (Dies ist der von den Apple-Ingenieuren vorgeschlagene Ansatz.) Die allgemeine Idee besteht darin, die Autolayout-Engine die Einschränkungen beim ersten Mal lösen zu lassen, dann die berechnete Höhe für diese Zelle zwischenzuspeichern und den zwischengespeicherten Wert für alle zukünftigen Anforderungen für die Höhe dieser Zelle zu verwenden. Der Trick besteht natürlich darin, sicherzustellen, dass Sie die zwischengespeicherte Höhe für eine Zelle löschen, wenn etwas passiert, das dazu führen kann, dass sich die Höhe der Zelle ändert. Dies ist in erster Linie dann der Fall, wenn sich der Inhalt dieser Zelle ändert oder wenn andere wichtige Ereignisse auftreten (z. B. das Anpassen durch den Benutzer) den Schieberegler für die Textgröße "Dynamischer Typ").

Generischer Beispielcode für iOS 7 (mit vielen saftigen Kommentaren)

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

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

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

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

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

    return cell;
}

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

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

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

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

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

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

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

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

    return height;
}

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

Beispielprojekte

Diese Projekte sind voll funktionsfähige Beispiele für Tabellenansichten mit variablen Zeilenhöhen aufgrund von Tabellenansichtszellen, die dynamischen Inhalt in UILabels enthalten.

Xamarin (C # /. NET)

Wenn Sie Xamarin verwenden, check this out Beispielprojekt zusammengestellt von @KentBoogaart .


1
Während dies gut funktioniert, finde ich es unter Schätzungen der erforderlichen Größen leicht (möglicherweise aufgrund verschiedener Rundungsprobleme) und ich muss ein paar Punkte zur endgültigen Höhe hinzufügen, damit mein gesamter Text in die Beschriftungen passt
DBD

5
@ Alex311 Sehr interessant, danke für dieses Beispiel. Ich habe ein wenig getestet und hier einige Kommentare geschrieben: github.com/Alex311/TableCellWithAutoLayout/commit/…
smileyborg

3
Ich würde DRINGEND empfehlen, die Zelle für jeden Identifikationstyp für die Wiederverwendung von Zellen zwischenzuspeichern. Jedes Mal, wenn Sie sich für die Höhe in die Warteschlange stellen, nehmen Sie eine Zelle aus der Warteschlange, die nicht wieder hinzugefügt wird. Durch das Zwischenspeichern kann die Häufigkeit, mit der der Initialisierer für Ihre Tabellenzellen aufgerufen wird, erheblich verringert werden. Aus irgendeinem Grund, wenn ich nicht zwischengespeichert habe, wuchs die Menge des verwendeten Speichers weiter, während ich scrollen würde.
Alex311

1
Storyboards eigentlich. Ich denke nicht, dass dies für Prototypzellen funktioniert (zumindest ohne die gesamte VC erneut zu instanziieren). Es kann möglich sein, das Leck insgesamt zu vermeiden heightForRowAtIndexPath, indem Sie die Zelle aus der Warteschlange entfernen , sie behalten und beim nächsten cellForRowAtIndexPathAufruf zurückgeben.
Nschum

2
Wird die iOS8Implementierung für iOS9und noch empfohlen iOS10, oder gibt es neue Ansätze, seit diese Antwort veröffentlicht wurde?
Koen

175

Für iOS 8 oben ist es wirklich einfach:

override func viewDidLoad() {  
    super.viewDidLoad()

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

oder

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

Für iOS 7 ist der Schlüssel jedoch die Berechnung der Höhe nach dem automatischen Layout:

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

Wichtig

  • Wenn mehrere Zeilen beschriftet sind, vergessen Sie nicht, das auf numberOfLineszu setzen 0.

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

Der vollständige Beispielcode ist hier .


7
Ich denke, wir müssen die Funktion heightForRowAtIndexPath im OS8-Fall nicht implementieren
jpulikkottil

Wie @eddwinpaz zu einer anderen Antwort feststellt, ist es wichtig, dass die Einschränkungen, die zum Sperren der Zeilenhöhe verwendet werden, KEINEN Rand enthalten.
Richard

1
+1 für "Wenn mehrere Zeilen beschriftet sind, vergessen Sie nicht, die Anzahl der Zeilen auf 0 zu setzen", was dazu führte, dass meine Zellen nicht dynamisch groß waren.
Ian-Fogelman

Nennen Sie mich einen Kontrollfreak, aber selbst in den Tagen von iOS 11 mag ich UITableViewAutomaticDimension immer noch nicht. Hatte in der Vergangenheit einige schlechte Erfahrungen damit. Daher verwende ich normalerweise die hier aufgeführten iOS7-Lösungen. Williams Hinweis, label.preferredMaxLayoutWidth hier nicht zu vergessen, hat mich gerettet.
Brian Sachetta

Wenn wir scrollen, erscheint das Etikett in mehreren Zeilen und die Höhe der Reihe nimmt ebenfalls nicht zu
Karanveer Singh,

93

Schnelles Beispiel für eine UITableViewCell mit variabler Höhe

Aktualisiert für Swift 3

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

Das fertige Projekt sollte folgendermaßen aussehen:

Geben Sie hier die Bildbeschreibung ein

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 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 ü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:

  • 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

Richten Sie das Storyboard ein

Fügen Sie Ihrem Ansichts-Controller eine Tabellenansicht hinzu und heften Sie sie mithilfe des automatischen Layouts an die vier Seiten. Ziehen Sie dann eine Tabellenansichtszelle auf die Tabellenansicht. Ziehen Sie auf die Prototypzelle ein Etikett. Verwenden Sie das automatische Layout, um die Beschriftung an den vier Kanten der Inhaltsansicht der Tabellenansichtszelle zu befestigen.

Geben Sie hier die Bildbeschreibung ein

Wichtige Notiz:

  • Das automatische Layout arbeitet mit den beiden oben genannten wichtigen Codezeilen zusammen. Wenn Sie kein automatisches Layout verwenden, funktioniert es nicht.

Andere IB-Einstellungen

Benutzerdefinierter Klassenname und Bezeichner

Wählen Sie die Tabellenansichtszelle aus und legen Sie die benutzerdefinierte Klasse fest MyCustomCell(den Namen der Klasse in der von uns hinzugefügten Swift-Datei). Setzen Sie auch den Bezeichner auf cell(dieselbe Zeichenfolge, die wir cellReuseIdentifierim obigen Code für verwendet haben.

Geben Sie hier die Bildbeschreibung ein

Nulllinien für Beschriftung

Stellen Sie die Anzahl der Zeilen 0in Ihrem Etikett ein. Dies bedeutet mehrzeilig und ermöglicht es dem Etikett, seine Größe basierend auf seinem Inhalt zu ändern.

Geben Sie hier die Bildbeschreibung ein

Schließen Sie die Steckdosen an

  • Steuern Sie das Ziehen aus der Tabellenansicht im Storyboard auf die tableViewVariable im ViewControllerCode.
  • Machen Sie dasselbe für das Label in Ihrer Prototype-Zelle mit der myCellLabelVariablen in der MyCustomCellKlasse.

Fertig

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

Anmerkungen

  • Dieses Beispiel funktioniert nur für iOS 8 und höher. Wenn Sie iOS 7 weiterhin unterstützen müssen, funktioniert dies für Sie nicht.
  • Ihre eigenen benutzerdefinierten Zellen in Ihren zukünftigen Projekten werden wahrscheinlich mehr als eine einzige Bezeichnung haben. Stellen Sie sicher, dass alles richtig fixiert ist, damit das automatische Layout die richtige Höhe bestimmen kann. Möglicherweise müssen Sie auch vertikale Kompressionsbeständigkeit und Umarmung verwenden. Weitere Informationen hierzu finden Sie in diesem Artikel .
  • Wenn Sie die Vorder- und Hinterkante (links und rechts) nicht fixieren, müssen Sie möglicherweise auch die Beschriftungen preferredMaxLayoutWidthso einstellen , dass sie wissen, wann der Zeilenumbruch erfolgen soll. Wenn Sie beispielsweise der Beschriftung im obigen Projekt eine horizontale Einschränkung für die horizontale Mitte hinzugefügt haben, anstatt die Vorder- und Hinterkante zu fixieren, müssen Sie diese Zeile zur tableView:cellForRowAtIndexPathMethode hinzufügen :

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Siehe auch


Ist es nicht wirklich besser, das preferredMaxLayoutWidthgegen die Zelle zu stellen contentSize? Auf diese Weise wird eine Zubehöransicht, die zum Bearbeiten überstrichen wurde, trotzdem berücksichtigt?
Honig

@Honey, du hast vielleicht recht. Ich habe seit ungefähr einem Jahr nicht mehr mit iOS Schritt gehalten und bin zu verrostet, um Ihnen jetzt gut zu antworten.
Suragch

65

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

Ich habe beschlossen, diese clevere Lösung von @smileyborg in eine UICollectionViewCell+AutoLayoutDynamicHeightCalculationKategorie einzuteilen .

Die Kategorie behebt auch die in der Antwort von @ wildmonkey beschriebenen Probleme (Laden einer Zelle von einer Feder und systemLayoutSizeFittingSize:Zurückkehren CGRectZero)

Es berücksichtigt kein Caching, entspricht aber gerade meinen Anforderungen. 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 nicht in iOS8 machen, aber da ist er jetzt!


2
Sie sollten einfach in der Lage sein, einfach ein: zu verwenden [YourCell new]und dieses als Dummy zu verwenden. Solange der Code zum Erstellen von Einschränkungscodes in Ihrer Instanz ausgelöst wird und Sie programmgesteuert einen Layoutdurchlauf auslösen, sollten Sie bereit sein.
Adam Waite

1
Vielen Dank! Das funktioniert. Ihre Kategorie ist großartig. Dadurch wurde mir klar, dass diese Technik auch funktioniert UICollectionViews.
Ricardo Sanchez-Saez

2
Wie würden Sie dies mit einer in einem Storyboard definierten Prototypzelle tun?
David Potter

57

Hier ist meine Lösung:

Sie müssen das sagen , TableViewdie , estimatedHeightbevor sie die Ansicht laden. 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
}

2
Das korrekte Einrichten des automatischen Layouts sowie das Hinzufügen dieses Codes in viewDidLoad haben den Trick ausgeführt.
Bruce

1
aber was ist, wenn estimatedRowHeightsich Zeile für Zeile ändert? sollte ich über- oder unterschätzen? Minimum oder Maximum der Höhe verwenden, in der ich benutze tableView?
János

1
@ János das ist der Punkt von rowHeight. Um dies wie erwartet zu tun, müssen Sie Einschränkungen ohne Ränder verwenden und die Objekte an der TableViewCell ausrichten. und ich nehme an, Sie verwenden eine UITextView, so dass Sie immer noch autoscroll = false entfernen müssen, da sonst die Höhe beibehalten wird und die relative Höhe nicht wie erwartet funktioniert.
Eddwin Paz

1
Das ist bei weitem die robusteste Lösung. Keine Sorge estimatedRowHeight, es wirkt sich hauptsächlich auf die Größe der Bildlaufleiste aus, niemals auf die Höhe der tatsächlichen Zellen. Seien Sie in der von Ihnen gewählten Höhe fett: Dies wirkt sich auf die Animation zum Einfügen / Löschen aus.
SwiftArchitect

Hier ist der Beispielcode in Swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

47

Die von @smileyborg vorgeschlagene Lösung ist nahezu perfekt. Wenn Sie eine benutzerdefinierte Zelle haben und eine oder mehrere UILabelmit dynamischen Höhen möchten, gibt die systemLayoutSizeFittingSize- Methode in Kombination mit aktiviertem AutoLayout a zurück, es CGSizeZerosei denn, Sie verschieben alle Ihre Zellenbeschränkungen aus der Zelle in die Inhaltsansicht (wie von @TomSwift hier vorgeschlagen) Alle Unteransichten mit Autolayout ausstatten? ).

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

Das Mischen der @ smileyborg-Antwort mit dieser sollte funktionieren.


systemLayoutSizeFittingSizemuss auf contentView aufgerufen werden, nicht auf cell
onmyway133

25

Ein wichtiger Punkt, auf den ich gerade gestoßen bin, um ihn als Antwort zu posten.

@ smileyborgs Antwort ist größtenteils richtig. Wenn Sie jedoch Code in der layoutSubviewsMethode Ihrer benutzerdefinierten Zellenklasse haben, z. B. das Festlegen von preferredMaxLayoutWidth, wird dieser nicht mit diesem Code ausgeführt:

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

Es hat mich eine Weile verwirrt. Dann wurde mir klar, dass diese nur layoutSubviews auf contentViewder Zelle auslösen , nicht auf der Zelle selbst.

Mein Arbeitscode sieht folgendermaßen 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 beim Erstellen einer neuen Zelle sicher nicht anrufen müssen, setNeedsLayoutda diese bereits festgelegt sein sollte. In Fällen, in denen Sie einen Verweis auf eine Zelle speichern, sollten Sie ihn wahrscheinlich aufrufen. In jedem Fall sollte es nichts tun.

Ein weiterer Tipp, wenn Sie Zellunterklassen verwenden, in denen Sie beispielsweise Einstellungen vornehmen preferredMaxLayoutWidth. Wie @smileyborg erwähnt, "wurde die Breite Ihrer Tabellenansichtszelle noch nicht auf die Breite der Tabellenansicht festgelegt". Dies ist richtig und problematisch, wenn Sie Ihre Arbeit in Ihrer Unterklasse und nicht im View Controller ausführen. Sie können den Zellenrahmen jedoch einfach an dieser Stelle mithilfe 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 speichere diese bestimmte Zelle zur Wiederverwendung zwischen, aber das ist irrelevant).



19

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.


Dies funktioniert für mich auf iOS7 ist es nun OK rufen tableView:cellForRowAtIndexPath:in tableView:heightForRowAtIndexPath:jetzt?
Ameisen

Ok , so ist dies nicht funktioniert, aber wenn ich rufe systemLayoutSizeFittingSize:in tableView:cellForRowAtIndexPath:und das Ergebnis dann zwischengespeichert und dann verwenden , in tableView:heightForRowAtIndexPath:es gut , so lange funktioniert , wie die Einschränkungen richtig natürlich Setup sind!
Ameisen

Dies funktioniert nur, wenn Sie dequeueReusableCellWithIdentifier: anstelle von dequeueReusableCellWithIdentifier: forIndexPath:
Antoine

1
Ich denke wirklich nicht, dass es ein guter Weg ist, tableView: cellForRowAtIndexPath: direkt aufzurufen.
Itachi

19

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

Beachten Sie das folgende Problem in Xcode 7.x, das Verwirrung stiften kann:

Der Interface Builder behandelt die Einrichtung der Zellen mit automatischer Größenanpassung nicht ordnungsgemäß. Auch wenn Ihre Einschränkungen absolut gültig sind, wird sich IB dennoch beschweren und Ihnen verwirrende Vorschläge und Fehler geben. Der Grund dafür ist, dass IB nicht bereit ist, die Höhe der Zeile gemäß Ihren Einschränkungen zu ändern (sodass die Zelle um Ihren Inhalt herum passt). Stattdessen bleibt die Höhe der Zeile unverändert und es wird vorgeschlagen, dass Sie Ihre Einschränkungen ändern, die Sie ignorieren sollten .

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

Geben Sie hier die Bildbeschreibung ein

Wenn Sie nun die Schriftgröße ändern (in diesem Beispiel ändere ich die Schriftgröße des Beschreibungsetiketts von 17.0 auf 18.0).

Geben Sie hier die Bildbeschreibung ein

Da die Schriftgröße erhöht wurde, möchte das Etikett jetzt 3 Zeilen belegen (zuvor waren es 2 Zeilen).

Wenn Interface Builder wie erwartet funktioniert, wird die Größe der Zelle an die neue Beschriftungshöhe angepasst. Was jedoch tatsächlich passiert, ist, dass IB das rote Symbol für den Fehler beim automatischen Layout anzeigt und vorschlägt, dass Sie die Prioritäten für das Umarmen / Komprimieren ändern.

Geben Sie hier die Bildbeschreibung ein

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

Geben Sie hier die Bildbeschreibung ein

Ich habe diese Höhe klickweise geändert (mit dem Stepper nach oben / unten), bis die Fehler mit dem roten Pfeil verschwunden sind! (Sie erhalten tatsächlich gelbe Warnungen. An diesem Punkt führen Sie einfach "Frames aktualisieren" durch. Es sollte alles funktionieren.)

* Beachten Sie, dass Sie diese roten oder gelben Warnungen im Interface Builder nicht beheben müssen - zur Laufzeit funktioniert alles ordnungsgemäß (auch wenn IB Fehler / Warnungen anzeigt). Stellen Sie einfach sicher, dass zur Laufzeit im Konsolenprotokoll keine AutoLayout-Fehler angezeigt werden.

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

Um die lästigen IB-Warnungen / Fehler zu vermeiden, können Sie die beteiligten Ansichten auswählen und Size Inspectorfür die Eigenschaft AmbiguityauswählenVerify Position Only

Geben Sie hier die Bildbeschreibung ein


Xcode 8.x / Xcode 9.x scheint (manchmal) Dinge anders zu machen als Xcode 7.x, aber immer noch falsch. Selbst wenn compression resistance priority/ hugging priorityauf "Erforderlich" (1000) gesetzt ist, kann Interface Builder beispielsweise eine Beschriftung dehnen oder abschneiden, um sie an die Zelle anzupassen (anstatt die Zellenhöhe so zu ändern, dass sie um die Beschriftung herum passt). In einem solchen Fall werden möglicherweise nicht einmal AutoLayout-Warnungen oder -Fehler angezeigt. Oder manchmal macht es genau das, was Xcode 7.x gemacht hat, wie oben beschrieben.


Hallo, ist es möglich, eine dynamische Höhe für die Zelle anzugeben, die eine Tabellenansicht mit einer Zelle mit dynamischem Zelleninhalt hat?
Jignesh B

18

Um die automatische Bemaßung für die Zeilenhöhe und die geschätzte Zeilenhöhe festzulegen, müssen Sie die folgenden Schritte ausführen, damit die automatische Bemaßung für das Layout der Zellen- / Zeilenhöhe wirksam wird.

  • Zuweisen und Implementieren von tableview dataSource und Delegieren
  • Zuweisen UITableViewAutomaticDimensionzu rowHeight & geschätzteRowHeight
  • Implementieren Sie delegate / dataSource-Methoden (dh heightForRowAtgeben Sie einen Wert zurück UITableViewAutomaticDimension)

- -

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

Schnell:

@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 Label-Instanz in UITableviewCell

  • Stellen Sie die Anzahl der Zeilen auf 0 ein (& Zeilenumbruchmodus = Schwanz abschneiden)
  • Legen Sie alle Einschränkungen (oben, unten, rechts links) in Bezug auf die Übersicht / den Zellencontainer fest.
  • Optional : Legen Sie die Mindesthöhe für das Etikett fest, wenn der minimale vertikale Bereich vom Etikett abgedeckt werden soll, auch wenn keine Daten vorhanden sind.

Geben Sie hier die Bildbeschreibung ein

Hinweis : Wenn Sie mehr als ein Etikett (UIElements) mit dynamischer Länge haben, das entsprechend seiner Inhaltsgröße angepasst werden sollte: Passen Sie die Priorität "Inhaltsumarmung und Kompressionswiderstand" für Etiketten an, die Sie mit höherer Priorität erweitern / komprimieren möchten.


1
Vielen Dank scheint eine klare und einfache Lösung zu sein, aber ich habe ein Problem. Ich verwende keine Beschriftungen, sondern Textansichten. Daher muss die Zeilenhöhe erhöht werden, wenn Daten hinzugefügt werden. Mein Problem ist dann, Informationen an heightForRowAt zu übergeben. Ich kann die Höhe meiner sich ändernden Textansichten messen, muss aber jetzt die Zeilenhöhe ändern. Würde mich über Hilfe freuen
Jeremy Andrews

@ JeremyAndrews Sicher wird Ihnen helfen. Stellen Sie Ihre Frage mit dem Quellcode, den Sie ausprobiert haben, und erläutern Sie das Problem.
Krunal

Es funktioniert für mich ohne Implementierung einer tableView: heightForRowDatenquelle.
Hemang

@Hemang - Seit iOS 11+ funktioniert es ohne tableView: heightForRow. (für iOS 10- tableView: heightForRowist erforderlich)
Krunal

1
@Hemang - Die Lösung hängt von der genauen Abfragedefinition ab. Hier habe ich allgemeine allgemeine Lösung für Ihre Anfrage. Setzen Sie Zustand in tableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
Krunal

16

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

Ich kämpfte eine Weile mit @ smileyborgs Antwort. Das Problem, auf das ich gestoßen bin, ist, wenn Sie Ihre Prototypzelle in IB mit zusätzlichen Elementen ( UILabels, UIButtonsusw.) in IB definiert haben, wenn Sie die Zelle mit [ [YourTableViewCellClass alloc] init]instanziieren, werden nicht alle anderen Elemente in dieser Zelle instanziiert, es sei denn, Sie haben geschriebener Code, 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 verursacht.) Wenn Sie dies tun, werden alle in IB definierten Elemente instanziiert.


15

Zellenhöhe in der dynamischen Tabellenansicht und automatisches Layout

Ein guter Weg, um das Problem mit dem automatischen Layout des Storyboards 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;
}

1
Dies wurde bereits in der akzeptierten Antwort auf diese Frage ausführlich behandelt.
Smileyborg

2
Ja, ich weiß ... aber ich wollte PureLayout nicht verwenden, und der dispatch_once-Trick hat mir sehr geholfen, es nur mit dem Storyboard herauszufinden.
Mohnasm

13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

Geben Sie hier die Bildbeschreibung ein


13

Eine weitere "Lösung": Überspringen Sie all diese Frustrationen und verwenden Sie stattdessen eine UIScrollView, um ein Ergebnis zu erhalten, das mit UITableView identisch aussieht und sich identisch 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, wie es Smileyborg vorgeschlagen hatte, und über viele Monate und drei Versionen von App Store-Versionen gescheitert war.

Meiner Meinung nach ist die Technologie einfach zu spröde, wenn Sie wirklich iOS 7-Unterstützung benötigen (für uns ist dies unerlässlich), und Sie werden sich beim Versuch die Haare aus dem Kopf ziehen. Und dass UITableView im Allgemeinen ein völliger Overkill ist, es sei denn, Sie verwenden einige der erweiterten Funktionen zur Zeilenbearbeitung und / oder müssen wirklich mehr als 1000 "Zeilen" unterstützen (in unserer App sind es realistisch gesehen nie mehr als 20 Zeilen).

Der zusätzliche Bonus ist, dass der Code wahnsinnig einfach wird im Vergleich zu all dem Delegierten-Mist und hin und her, der mit UITableView geliefert wird. Es ist nur eine einzige Codeschleife in viewOnLoad, die elegant aussieht und einfach zu verwalten ist.

Hier sind einige Tipps dazu:

  1. Erstellen Sie mit Storyboard oder einer NIB-Datei einen ViewController und die zugehörige Stammansicht.

  2. Ziehen Sie über eine UIScrollView in Ihre Stammansicht.

  3. Fügen Sie der Ansicht der obersten Ebene Einschränkungen oben, unten, links und rechts hinzu, damit die UIScrollView die gesamte Stammansicht ausfü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 Breite" hinzu, um UIScrollView und UIView zu verknüpfen.

    HINWEIS: Sie erhalten die Fehlermeldung "Die Bildlaufansicht hat eine mehrdeutige Höhe des scrollbaren Inhalts" und dass Ihre Container-UIView eine Höhe von 0 Pixel haben sollte. Keiner der Fehler scheint von Bedeutung zu sein, wenn die App ausgeführt wird.

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

  6. In Ihrem Root-ViewController fügen Sie im Wesentlichen alle "Zeilen" zur Container-UIView hinzu und fügen programmgesteuert Einschränkungen hinzu, die den linken und rechten Rand mit der Containeransicht, die oberen Kanten entweder mit der Containeransicht oben (für das erste Element) oder dem vorherigen verknüpfen Zelle. Verbinden Sie dann die letzte Zelle mit dem Behälterboden.

Für 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 bei diesem Ansatz gefunden habe, ist, dass UITableView eine nette Funktion von "schwebenden" Abschnittsüberschriften oben in der Ansicht hat, während Sie scrollen. Die obige Lösung wird dies nur tun, wenn Sie mehr Programmierung hinzufügen. In unserem speziellen Fall war diese Funktion jedoch nicht zu 100% erforderlich, und niemand bemerkte, wann sie nicht mehr vorhanden war.

Wenn Sie Teiler zwischen Ihren Zellen wünschen, fügen Sie einfach eine 1 Pixel hohe UIView am unteren Rand Ihrer benutzerdefinierten "Zelle" hinzu, die wie ein Teiler aussieht.

Stellen Sie sicher, dass "Bounces" und "Bounce Vertical" aktiviert sind, damit die Aktualisierungssteuerung funktioniert. Es scheint also eher eine Tabellenansicht zu sein.

TableView zeigt einige leere Zeilen und Teiler unter Ihrem Inhalt an, wenn es nicht den gesamten Bildschirm ausfüllt, wie dies bei dieser Lösung nicht der Fall ist. Aber ich persönlich bevorzuge es, wenn diese leeren Zeilen sowieso nicht da wären - bei variabler Zellenhöhe sah es für mich sowieso immer "fehlerhaft" aus, die leeren Zeilen dort zu haben.

Wir hoffen, dass ein anderer Programmierer meinen Beitrag liest, bevor er mehr als 20 Stunden damit verschwendet, ihn mit Table View in seiner eigenen App herauszufinden. :) :)


11

Ich musste dynamische Ansichten verwenden (Setup-Ansichten und Einschränkungen nach Code) und als ich die Breite des bevorzugten MaxLayoutWidth-Labels festlegen wollte, war 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 wie erwartet und die dynamische Höhe wurde richtig berechnet.


9

Angenommen, Sie haben eine Zelle mit einer Unteransicht und möchten, dass die Höhe der Zelle hoch genug ist, um die Unteransicht + das Auffüllen zu umfassen.

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

2) Setzen Sie entweder die rowHeightEigenschaft tableView oder tableView:heightForRowAtIndexPath:auf UITableViewAutomaticDimension.

3) Legen Sie entweder die estimatedRowHeightEigenschaft von tableView oder tableView:estimatedHeightForRowAtIndexPath:eine bestmögliche Schätzung der Höhe fest.

Das ist es.


7

Wenn Sie programmgesteuert gestalten, sollten Sie Folgendes für iOS 10 beachten, indem Sie Anker in Swift verwenden.

Es gibt drei Regeln / Schritte

NUMMER 1: Legen Sie diese beiden Eigenschaften der Tabellenansicht in viewDidLoad fest. Die erste teilt der Tabellenansicht mit, dass dynamische Größen für ihre Zellen erwartet werden sollen. Die zweite dient lediglich dazu, die App die Größe des Bildlaufleistenindikators berechnen zu lassen, damit dies hilfreich ist Performance.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMMER 2: Dies ist wichtig. Sie müssen die Unteransichten zur Inhaltsansicht der Zelle hinzufügen, nicht zur Ansicht, und die Layoutansichten verwenden, um die Unteransichten oben und unten zu verankern. Dies ist ein funktionierendes Beispiel dafür.

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, und rufen Sie sie 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


in Swift 5 UITableViewAutomaticDimension umbenannt in UITableView.automaticDimension
James Rochabrun

4

Wenn Sie eine lange Zeichenfolge haben. zB eine, die keinen Zeilenumbruch hat. Dann könnten Sie auf einige Probleme stoßen.

Die "angebliche" Korrektur 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 Suraghs Antwort am vollständigsten und prägnantesten , daher nicht verwirrend.

Erklären Sie jedoch nicht, warum diese Änderungen erforderlich sind. Lass uns das tun.

Fügen Sie den folgenden Code in ein Projekt ein.

import UIKit

class ViewController: UIViewController {

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

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

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

Beachten Sie, dass ich keine Größenbeschränkungen hinzugefügt habe . Ich habe nur CenterX- und CenterY-Einschränkungen hinzugefügt. Trotzdem wird das Etikett die richtige Größe haben. Warum?

Wegen contentSize.

Um dies besser zu verarbeiten, behalten Sie zuerst Schritt 0 bei und kommentieren Sie dann die Schritte 1 bis 6 aus. Lass setupLayout()bleiben. Beobachten Sie das Verhalten.

Dann Kommentar 1 auskommentieren und beobachten.

Dann Kommentar 2 auskommentieren und beobachten.

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

Was kann daraus geschlossen werden? Welche Faktoren können das ändern contenSize?

  1. Textlänge: Wenn Sie einen längeren Text haben, erhöht sich die Breite Ihres intrinsicContentSize
  2. Zeilenumbrüche: Wenn Sie hinzufügen \n, entspricht die Breite von 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 zulässigen Zeilen: Sie müssen die Option auf einstellen numberOfLines, da 0sonst nicht mehrere Zeilen vorhanden sind. Sie numberOfLinespassen die Höhe Ihres intrinsicContentSize an
  4. Vornehmen von Anpassungen: Stellen Sie sich vor, basierend auf Ihrem Text war die Breite 200und Höhe Ihres intrinsicContentSize 100, aber Sie wollten die Breite auf den Container des Etiketts beschränken. Was werden Sie tun? Die Lösung besteht darin, die gewünschte Breite einzustellen. Sie tun das , indem Sie preferredMaxLayoutWidthzu 130Ihrem neuen intrinsicContentSize dann etwa eine Breite von haben 130. Die Höhe wäre offensichtlich größer als 100weil Sie mehr Linien benötigen würden. Davon abgesehen, wenn Ihre Einschränkungen richtig eingestellt sind, müssen Sie dies überhaupt nicht verwenden! Weitere Informationen hierzu finden Sie in dieser Antwort und ihren Kommentaren. Sie müssen nur GebrauchpreferredMaxLayoutWidth wenn Sie keine Einschränkungen haben, die die Breite / Höhe einschränken, wie in "Man darf den Text nicht umbrechen, es sei denn, er überschreitet die."preferredMaxLayoutWidth". Aber mit 100% iger Sicherheit, wenn Sie den Leading / Trailing einstellen und numberOfLinesbis 0dahin sind Sie gut!Lange Rede, kurzer Sinn, die meisten Antworten hier, die die Verwendung empfehlen, sind FALSCH! Du brauchst es nicht. Die Notwendigkeit ist 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 auch, dass sich die Höhe von intrinsicContentSize erhöht, wenn Sie Ihre Schriftgröße erhöhen. Das habe ich in meinem Code nicht gezeigt. Sie können das selbst versuchen.

Zurück zu Ihrem tableViewCell-Beispiel:

Alles was Sie tun müssen ist:

  • setze das numberOfLinesauf0
  • Beschränken Sie das Etikett korrekt auf die Ränder / Kanten
  • Es muss nicht eingestellt werden preferredMaxLayoutWidth.

1

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

das habe ich hier in meiner antwort mit autolayout und programmatisch erreicht:

Grundsätzlich hat die Antwort über @smileyBorg 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ötigen Update-Einschränkungen erforderlich. 4. Keine Verwendung der automatischen bevorzugten maximalen Layoutbreite. 5. Keine Verwendung von systemLayoutSizeFittingSize (sollte verwendet werden, funktioniert aber nicht für mich, ich weiß nicht, was es intern tut), sondern meine Methode - (float) getViewHeight funktioniert und ich weiß, was es intern tut.

Ist es möglich, dass eine UITableView-Zelle unterschiedliche Höhen aufweist, wenn ich die Zelle auf verschiedene Arten anzeige?


1

In meinem Fall lag das Auffüllen an den Höhen von sectionHeader und sectionFooter, bei denen ich es im Storyboard auf mindestens 1 ändern konnte. In der viewDidLoad-Methode also:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

1

Ich habe nur ein paar dumme Versuche mit den 2 Werten von rowHeightund gemacht estimatedRowHeightund dachte nur, es könnte einen Einblick in das Debuggen geben:

Wenn Sie beide ODER nur die estimatedRowHeighteinstellen, erhalten Sie 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 richtige Schätzung zu erhalten, aber das Endergebnis ist nicht anders. Dies wirkt sich nur auf Ihre Leistung aus.

Geben Sie hier die Bildbeschreibung ein


Wenn Sie nur die rowHeight einstellen, dh nur:

tableView.rowHeight = UITableViewAutomaticDimension

Ihr Endergebnis wäre nicht wie gewünscht:

Geben Sie hier die Bildbeschreibung ein


Wenn Sie den Wert estimatedRowHeightauf 1 oder kleiner setzen, stürzen Sie unabhängig vom ab 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

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

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

in einigen Fällen unzuverlässig zu sein, in denen Einschränkungen nicht eindeutig sind. Es ist besser, die Layout-Engine zu zwingen, die Höhe in eine Richtung zu berechnen, indem Sie die Hilfskategorie in UIView unten 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


0

Fügen Sie einfach diese beiden Funktionen in Ihren Viewcontroller ein, um Ihr Problem zu lösen. Hier ist list ein String-Array, das den String 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
}

-1
swift 4

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

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

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

//Remove observer
 deinit {

        NotificationCenter.default.removeObserver(self)

    }

-1

Wenn die Zellenhöhe durch den Inhalt dynamisch ist, sollten Sie sie genau zählen und dann den Höhenwert zurückgeben, bevor die Zelle gerendert wird. Eine einfache Möglichkeit besteht darin, die Zählmethode im Zellencode der Tabellenansicht zu definieren, damit der Controller die Delegatenmethode für die Tabellenzellenhöhe aufruft. Vergessen Sie nicht, die tatsächliche Breite des Zellenrahmens zu zählen (Standard ist 320), wenn die Höhe von der Breite der Tabelle oder des Bildschirms abhängt. Das heißt, verwenden Sie in der Delegierungsmethode für Tabellenzellenhöhen zuerst cell.frame, um die Zellenbreite zu korrigieren, und rufen Sie dann die in der Zelle definierte Zählhöhenmethode auf, um den geeigneten Wert zu erhalten und ihn zurückzugeben .

PS. Der Code zum Generieren eines Zellenobjekts könnte in einer anderen Methode definiert werden, damit eine andere Zelldelegatmethode für Tabellenansichten aufgerufen werden kann.


-3

UITableView.automaticDimension kann über Interface Builder eingestellt werden:

Xcode> Storyboard> Größeninspektor

Tabellenansicht Zelle> Zeilenhöhe> Automatisch

Größeninspektor


-4

eine weitere iOs7 + iOs8-Lösung in Swift

var cell2height:CGFloat=44

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

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

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height
    }
}

Hinweis: SystemLayoutSizeFittingSize funktioniert in meinem Fall nicht
DJDance

Die Zellenhöhe ist in nicht korrekt cellForRowAtIndexPath, die Zelle ist zu diesem Zeitpunkt noch nicht ausgelegt.
Andrii Chernenko

in iOs7 ist es ein fester Wert, dh es funktioniert. Sie können außerhalb von cellForRowAtIndexPath einstellen, wenn Sie möchten
djdance
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.