Utilizzo del layout automatico in UITableView per layout di celle dinamici e altezze di riga variabili


1501

Come si utilizza il layout automatico all'interno di UITableViewCells in una vista tabella per consentire al contenuto e alle visualizzazioni secondarie di ciascuna cella di determinare l'altezza della riga (se stessa / automaticamente), pur mantenendo prestazioni di scorrimento regolari?


Ecco il codice di esempio in swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur,

Per comprendere la maggior parte delle risposte sull'avere un UILabel multilinea, è necessario comprendere appieno come contentSizee come preferredMaxLayoutWidthlavorare. Vedi qui . Detto questo, se imposti correttamente i tuoi vincoli, non dovresti averne bisogno preferredMaxLayoutWidthe in effetti puoi creare risultati inaspettati.
Miele

Risposte:


2389

TL; DR: Non ti piace leggere? Passa direttamente ai progetti di esempio su GitHub:

Descrizione concettuale

I primi 2 passaggi seguenti sono applicabili indipendentemente dalle versioni di iOS per cui stai sviluppando.

1. Imposta e aggiungi vincoli

Nella UITableViewCellsottoclasse, aggiungi i vincoli in modo tale che le viste secondarie della cella abbiano i bordi appuntati ai bordi del contentView della cella (soprattutto ai bordi superiore E inferiore). NOTA: non appuntare le visualizzazioni secondarie sulla cella stessa; solo per la cella contentView! Consenti alle dimensioni intrinseche del contenuto di queste visualizzazioni secondarie di guidare l'altezza della visualizzazione del contenuto della cella di visualizzazione tabella assicurandosi che la resistenza alla compressione del contenuto e i vincoli di abbracciamento del contenuto nella dimensione verticale per ciascuna subview non vengano sovrascritti da vincoli con priorità più alta che hai aggiunto. ( Huh? Clicca qui. )

Ricorda, l'idea è quella di avere le viste secondarie della cella collegate verticalmente alla vista del contenuto della cella in modo che possano "esercitare pressione" e far espandere la vista del contenuto per adattarli. Utilizzando una cella di esempio con alcune visualizzazioni secondarie, ecco un'illustrazione visiva di come dovrebbero apparire alcuni (non tutti!) Dei tuoi vincoli:

Esempio di illustrazione di vincoli su una cella della vista tabella.

Puoi immaginare che quando viene aggiunto più testo all'etichetta del corpo a più righe nella cella di esempio sopra, dovrà crescere verticalmente per adattarsi al testo, il che costringerà effettivamente la cella a crescere in altezza. (Ovviamente, devi avere i vincoli giusti affinché questo funzioni correttamente!)

Ottenere i vincoli giusti è sicuramente la parte più difficile e più importante per ottenere altezze dinamiche delle celle lavorando con Auto Layout. Se commetti un errore qui, potrebbe impedire a tutto il resto di funzionare, quindi prenditi il ​​tuo tempo! Ti consiglio di impostare i tuoi vincoli nel codice perché sai esattamente quali vincoli vengono aggiunti dove, ed è molto più facile eseguire il debug quando le cose vanno male. L'aggiunta di vincoli nel codice può essere altrettanto semplice e significativamente più potente di Interface Builder utilizzando gli ancoraggi di layout o una delle fantastiche API open source disponibili su GitHub.

  • Se stai aggiungendo vincoli nel codice, dovresti farlo una volta all'interno del updateConstraintsmetodo della sottoclasse UITableViewCell. Nota che updateConstraintspuò essere chiamato più di una volta, quindi per evitare di aggiungere gli stessi vincoli più di una volta, assicurati di racchiudere il tuo codice di aggiunta del vincolo updateConstraintsin un controllo per una proprietà booleana come didSetupConstraints(che imposti su SÌ dopo aver eseguito il tuo vincolo -adding code una volta). D'altra parte, se si dispone di codice che aggiorna i vincoli esistenti (come ad esempio la regolazione della constantproprietà su alcuni vincoli), posizionarlo all'interno updateConstraintsma al di fuori del controllo per didSetupConstraintsconsentirne l'esecuzione ogni volta che viene chiamato il metodo.

2. Determinare gli identificatori univoci di riutilizzo delle celle della vista tabella

Per ogni set univoco di vincoli nella cella, utilizzare un identificatore di riutilizzo della cella univoco. In altre parole, se le tue celle hanno più di un layout univoco, ogni layout univoco dovrebbe ricevere il proprio identificatore di riutilizzo. (Un buon suggerimento che è necessario utilizzare un nuovo identificatore di riutilizzo è quando la variante di cella ha un diverso numero di sottopagine o le sottopagine sono disposte in modo distinto.)

Ad esempio, se stavi visualizzando un messaggio e-mail in ogni cella, potresti avere 4 layout univoci: messaggi con solo un oggetto, messaggi con un oggetto e un corpo, messaggi con un oggetto e un allegato fotografico e messaggi con un oggetto, corpo e allegato fotografico. Ogni layout ha vincoli completamente diversi richiesti per raggiungerlo, quindi una volta inizializzata la cella e aggiunti i vincoli per uno di questi tipi di celle, la cella dovrebbe ottenere un identificatore di riutilizzo univoco specifico per quel tipo di cella. Ciò significa che quando si dequeue una cella per il riutilizzo, i vincoli sono già stati aggiunti e sono pronti per scegliere quel tipo di cella.

Nota che a causa delle differenze nella dimensione del contenuto intrinseco, le celle con gli stessi vincoli (tipo) possono avere altezze diverse! Non confondere layout fondamentalmente diversi (vincoli diversi) con frame di vista calcolati diversi (risolti da vincoli identici) a causa delle diverse dimensioni del contenuto.

  • Non aggiungere celle con insiemi di vincoli completamente diversi allo stesso pool di riutilizzo (ovvero utilizzare lo stesso identificatore di riutilizzo) e quindi tentare di rimuovere i vecchi vincoli e impostare nuovi vincoli da zero dopo ogni dequeue. Il motore di layout automatico interno non è progettato per gestire cambiamenti su larga scala nei vincoli e vedrai enormi problemi di prestazioni.

Per iOS 8 - Celle a dimensionamento automatico

3. Abilita stima altezza riga

Per abilitare le celle della vista tabella con ridimensionamento automatico, è necessario impostare la proprietà rowHeight della vista tabella su UITableViewAutomaticDimension. È inoltre necessario assegnare un valore alla proprietà stimataRowHeight. Non appena vengono impostate entrambe queste proprietà, il sistema utilizza Auto Layout per calcolare l'altezza effettiva della riga

Apple: utilizzo delle celle di visualizzazione tabella con ridimensionamento automatico

Con iOS 8, Apple ha interiorizzato gran parte del lavoro che in precedenza doveva essere implementato da te prima di iOS 8. Per consentire il funzionamento del meccanismo della cella con ridimensionamento automatico, devi prima impostare la rowHeightproprietà nella vista tabella sulla costante UITableViewAutomaticDimension. Quindi, devi semplicemente abilitare la stima dell'altezza della riga impostando la estimatedRowHeightproprietà della vista tabella su un valore diverso da zero, ad esempio:

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

Ciò consente di fornire alla vista tabella un preventivo / segnaposto temporaneo per le altezze delle righe delle celle che non sono ancora sullo schermo. Quindi, quando queste celle stanno per scorrere sullo schermo, verrà calcolata l'altezza effettiva della riga. Per determinare l'altezza effettiva per ogni riga, la vista tabella chiede automaticamente a ciascuna cella quale altezza contentViewdeve basarsi sulla larghezza fissa nota della vista contenuto (che si basa sulla larghezza della vista tabella, meno eventuali elementi aggiuntivi come un indice di sezione o vista accessori) e i vincoli di layout automatico aggiunti alla vista contenuto e alle viste secondarie della cella. Una volta determinata l'altezza effettiva della cella, la vecchia altezza stimata per la riga viene aggiornata con la nuova altezza effettiva (e tutte le regolazioni del contenuto della vista tabella Dimensione / contenutoOffset vengono eseguite secondo necessità).

In generale, la stima fornita non deve essere molto accurata: viene utilizzata solo per dimensionare correttamente l'indicatore di scorrimento nella vista tabella e la vista tabella fa un buon lavoro di regolazione dell'indicatore di scorrimento per stime errate mentre scorrere le celle sullo schermo. È necessario impostare la estimatedRowHeightproprietà nella vista tabella (in viewDidLoado simile) su un valore costante che sia l'altezza della riga "media". Solo se le tue altezze di riga hanno un'estrema variabilità (ad esempio differiscono di un ordine di grandezza) e noti l'indicatore di scorrimento "saltare" mentre scorri, dovresti preoccuparti di implementare tableView:estimatedHeightForRowAtIndexPath:per fare il calcolo minimo richiesto per restituire una stima più accurata per ogni riga.

Per il supporto di iOS 7 (implementazione del ridimensionamento automatico delle celle)

3. Esegui un passaggio layout e ottieni l'altezza della cella

Innanzitutto, istanza un'istanza fuori schermo di una cella della vista tabella, un'istanza per ciascun identificatore di riutilizzo , utilizzata rigorosamente per i calcoli dell'altezza. (Offscreen significa che il riferimento di cella è memorizzato in una proprietà / ivar sul controller di visualizzazione e non è mai stato restituito tableView:cellForRowAtIndexPath:per la visualizzazione della tabella per essere effettivamente visualizzato sullo schermo.) Successivamente, la cella deve essere configurata con il contenuto esatto (ad es. Testo, immagini, ecc.) che sarebbe valido se dovesse essere visualizzato nella vista tabella.

Quindi, forzare la cella a impaginare immediatamente le sue visualizzazioni secondarie, quindi utilizzare il systemLayoutSizeFittingSize:metodo su UITableViewCell"s" contentViewper scoprire qual è l'altezza richiesta della cella. Utilizzare UILayoutFittingCompressedSizeper ottenere le dimensioni minime richieste per adattarsi a tutto il contenuto della cella. L'altezza può quindi essere restituita dal tableView:heightForRowAtIndexPath:metodo delegato.

4. Utilizzare le altezze di riga stimate

Se la vista della tabella contiene più di una dozzina di righe, scoprirai che eseguire la risoluzione dei vincoli del layout automatico può rapidamente impantanare il thread principale al primo caricamento della vista della tabella, come tableView:heightForRowAtIndexPath:viene chiamato su ogni riga al primo caricamento ( per calcolare la dimensione dell'indicatore di scorrimento).

A partire da iOS 7, puoi (e assolutamente dovresti) usare la estimatedRowHeightproprietà nella vista tabella. Ciò consente di fornire alla vista tabella un preventivo / segnaposto temporaneo per le altezze delle righe delle celle che non sono ancora sullo schermo. Quindi, quando queste celle stanno per scorrere sullo schermo, verrà calcolata l'altezza della riga effettiva (chiamando tableView:heightForRowAtIndexPath:) e l'altezza stimata verrà aggiornata con quella effettiva.

In generale, la stima fornita non deve essere molto accurata: viene utilizzata solo per dimensionare correttamente l'indicatore di scorrimento nella vista tabella e la vista tabella fa un buon lavoro di regolazione dell'indicatore di scorrimento per stime errate mentre scorrere le celle sullo schermo. È necessario impostare la estimatedRowHeightproprietà nella vista tabella (in viewDidLoado simile) su un valore costante che sia l'altezza della riga "media". Solo se le tue altezze di riga hanno un'estrema variabilità (ad esempio differiscono di un ordine di grandezza) e noti l'indicatore di scorrimento "saltare" mentre scorri, dovresti preoccuparti di implementare tableView:estimatedHeightForRowAtIndexPath:per fare il calcolo minimo richiesto per restituire una stima più accurata per ogni riga.

5. (Se necessario) Aggiungi cache altezza riga

Se hai fatto tutto quanto sopra e stai ancora scoprendo che le prestazioni sono inaccettabilmente lente quando esegui la risoluzione dei vincoli tableView:heightForRowAtIndexPath:, sfortunatamente dovrai implementare un po 'di cache per le altezze delle celle. (Questo è l'approccio suggerito dagli ingegneri di Apple.) L'idea generale è di lasciare che il motore Autolayout risolva i vincoli la prima volta, quindi memorizza nella cache l'altezza calcolata per quella cella e usa il valore memorizzato nella cache per tutte le richieste future per quella cella. Il trucco ovviamente è assicurarsi di cancellare l'altezza memorizzata nella cache per una cella quando succede qualcosa che potrebbe causare l'altezza della cella - principalmente, ciò accadrebbe quando cambia il contenuto di quella cella o quando si verificano altri eventi importanti (come la regolazione dell'utente il dispositivo di scorrimento della dimensione del testo Tipo dinamico).

Codice di esempio generico iOS 7 (con molti commenti succosi)

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

Progetti di esempio

Questi progetti sono esempi completamente funzionanti di viste di tabelle con altezze di riga variabili dovute a celle di viste di tabelle contenenti contenuto dinamico in UILabels.

Xamarin (C # /. NET)

Se stai usando Xamarin, dai un'occhiata a questo progetto di esempio messo insieme da @KentBoogaart .


1
Mentre funziona bene, lo trovo sotto stima delle dimensioni richieste leggermente (probabilmente a causa di vari problemi di arrotondamento) e devo aggiungere un paio di punti all'altezza finale per far sì che tutto il mio testo si adatti alle etichette
DBD

5
@ Alex311 Molto interessante, grazie per aver fornito questo esempio. Ho fatto un piccolo test da parte mia e ho scritto alcuni commenti qui: github.com/Alex311/TableCellWithAutoLayout/commit/…
smileyborg

3
Consiglio vivamente la memorizzazione nella cache della cella per ogni tipo di identificatore di riutilizzo delle celle. Ogni volta che si accoda per l'altezza, si estrae una cella dalla coda che non viene aggiunta di nuovo. La memorizzazione nella cache può ridurre significativamente il numero di volte in cui viene chiamato l'inizializzatore per le celle della tabella. Per qualche ragione, quando non stavo memorizzando nella cache, la quantità di memoria utilizzata continuava a crescere mentre scorrevo.
Alex311,

1
Storyboard, in realtà. Non penso che funzioni per le celle prototipo (almeno senza ri-istanziare l'intero VC). Potrebbe essere possibile evitare del tutto la perdita rimuovendo il collegamento heightForRowAtIndexPath, mantenendo la cella e restituendola la volta successiva cellForRowAtIndexPath.
nschum,

2
L' iOS8implementazione è ancora consigliata iOS9e iOS10, o ci sono nuovi approcci da quando questa risposta è stata pubblicata?
Koen

175

Per iOS 8 sopra è davvero semplice:

override func viewDidLoad() {  
    super.viewDidLoad()

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

o

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

Ma per iOS 7, la chiave è calcolare l'altezza dopo il completamento automatico:

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

Importante

  • Se si etichettano più righe, non dimenticare di impostare numberOfLinessu 0.

  • Non dimenticare label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

Il codice di esempio completo è qui .


7
Penso che non abbiamo bisogno di implementare la funzione heightForRowAtIndexPath nel caso OS8
jpulikkottil,

Come nota @eddwinpaz su un'altra risposta, è importante che i vincoli utilizzati per bloccare l'altezza della riga NON includano il margine.
Richard,

1
+1 per "Se le etichette con più righe, non dimenticare di impostare il numero di file su 0", ciò significava che le dimensioni delle mie celle non erano dinamiche.
Ian-Fogelman,

Chiamami un maniaco del controllo ma anche ai tempi di iOS 11 non mi piace ancora usare UITableViewAutomaticDimension. Ho avuto alcune brutte esperienze con esso in passato. Come tale, in genere utilizzo le soluzioni iOS7 elencate qui. La nota di William da non dimenticare label.preferredMaxLayoutWidth qui mi ha salvato.
Brian Sachetta,

Quando scorriamo, l'etichetta inizia a comparire su più righe e anche l'altezza della riga non aumenta
Karanveer Singh,

94

Rapido esempio di UITableViewCell ad altezza variabile

Aggiornato per Swift 3

La risposta rapida di William Hu è buona, ma mi aiuta ad avere alcuni passaggi semplici ma dettagliati quando imparo a fare qualcosa per la prima volta. L'esempio che segue è il mio progetto di test mentre imparo a creare un UITableViewaltezza cellulare variabile. L'ho basato su questo esempio di base di UITableView per Swift .

Il progetto finito dovrebbe apparire così:

inserisci qui la descrizione dell'immagine

Crea un nuovo progetto

Può essere solo un'applicazione a visualizzazione singola.

Aggiungi il codice

Aggiungi un nuovo file Swift al tuo progetto. Denominalo MyCustomCell. Questa classe conterrà gli sbocchi per le visualizzazioni che aggiungi alla tua cella nello storyboard. In questo esempio di base avremo solo un'etichetta in ogni cella.

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

Collegheremo questa presa più tardi.

Apri ViewController.swift e assicurati di avere il seguente contenuto:

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

Nota importante:

  • Sono le seguenti due righe di codice (insieme al layout automatico) che rendono possibile l'altezza della cella variabile:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension

Imposta lo storyboard

Aggiungi una vista tabella al controller della vista e usa il layout automatico per bloccarlo sui quattro lati. Quindi trascina una cella della vista tabella nella vista tabella. E sulla cella del prototipo, trascina un'etichetta. Usa il layout automatico per aggiungere l'etichetta ai quattro bordi della vista contenuto della cella Vista tabella.

inserisci qui la descrizione dell'immagine

Nota importante:

  • Il layout automatico funziona insieme alle due importanti linee di codice che ho menzionato sopra. Se non si utilizza il layout automatico, non funzionerà.

Altre impostazioni IB

Nome classe e identificatore personalizzati

Seleziona la cella di visualizzazione tabella e imposta la classe personalizzata come MyCustomCell(il nome della classe nel file Swift che abbiamo aggiunto). Imposta anche l'identificatore su cell(la stessa stringa che abbiamo usato per cellReuseIdentifiernel codice sopra.

inserisci qui la descrizione dell'immagine

Linee zero per etichetta

Imposta il numero di righe su 0nella tua etichetta. Ciò significa multilinea e consente all'etichetta di ridimensionarsi in base al suo contenuto.

inserisci qui la descrizione dell'immagine

Collega le prese

  • Controlla il trascinamento dalla vista tabella nello storyboard alla tableViewvariabile nel ViewControllercodice.
  • Fare lo stesso per l'etichetta nella cella del prototipo sulla myCellLabelvariabile nella MyCustomCellclasse.

Finito

Dovresti essere in grado di eseguire il tuo progetto ora e ottenere celle con altezze variabili.

Appunti

  • Questo esempio funziona solo per iOS 8 e versioni successive. Se hai ancora bisogno di supportare iOS 7, questo non funzionerà per te.
  • Le tue celle personalizzate nei tuoi progetti futuri avranno probabilmente più di una singola etichetta. Assicurati di avere tutto bloccato in modo che il layout automatico possa determinare l'altezza corretta da usare. Potrebbe anche essere necessario utilizzare la resistenza alla compressione verticale e l'abbraccio. Vedi questo articolo per saperne di più.
  • Se non stai bloccando i bordi iniziale e finale (sinistro e destro), potresti anche dover impostare le etichette in preferredMaxLayoutWidthmodo che sappia quando avvolgere la linea. Ad esempio, se si fosse aggiunto un vincolo Centra orizzontalmente all'etichetta nel progetto sopra anziché bloccare i bordi iniziale e finale, sarebbe necessario aggiungere questa linea al tableView:cellForRowAtIndexPathmetodo:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Guarda anche


Non è davvero meglio impostare il preferredMaxLayoutWidthcontro del cellulare contentSize? In questo modo se si dispone di un accessorio View o è stato trascinato per la modifica, sarebbe ancora preso in considerazione?
Miele

@Ciao, hai benissimo ragione. Non rimango su iOS per circa un anno e sono troppo arrugginito per risponderti bene in questo momento.
Suragch,

65

Ho racchiuso la soluzione iOS7 di @ smileyborg in una categoria

Ho deciso di includere questa intelligente soluzione di @smileyborg in una UICollectionViewCell+AutoLayoutDynamicHeightCalculationcategoria.

La categoria corregge anche i problemi descritti nella risposta di @ wildmonkey (caricamento di una cella da un pennino e systemLayoutSizeFittingSize:ritorno CGRectZero)

Non tiene conto della memorizzazione nella cache ma soddisfa le mie esigenze in questo momento. Sentiti libero di copiarlo, incollarlo e hackerarlo.

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

Esempio di utilizzo:

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

Per fortuna non dovremo fare questo jazz su iOS8, ma è lì per ora!


2
Dovresti essere in grado di usare semplicemente un: [YourCell new]e usarlo come manichino. Fintanto che il codice di creazione del codice di vincolo viene attivato nella tua istanza e attivi un passaggio di layout a livello di codice, dovresti essere pronto.
Adam Waite,

1
Grazie! Questo funziona La tua categoria è fantastica. È ciò che mi ha fatto capire che anche questa tecnica funziona UICollectionViews.
Ricardo Sanchez-Saez,

2
Come faresti usando una cellula prototipo definita in uno storyboard?
David Potter,

57

Ecco la mia soluzione:

È necessario dire al TableViewla estimatedHeightprima di caricare la vista. Altrimenti non sarà in grado di comportarsi come previsto.

Objective-C

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

Aggiornamento a Swift 4.2

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

2
Configurare correttamente il layout automatico, insieme a questo codice aggiunto in viewDidLoad ha funzionato.
Bruce,

1
ma cosa succede se estimatedRowHeightvaria riga per riga? dovrei sopra o sotto stima? uso minimo o massimo dell'altezza in cui uso tableView?
János,

1
@János questo è il punto di fila Altezza. Per fare ciò, come previsto, è necessario utilizzare i vincoli senza margini e allinearli con TableViewCell agli oggetti. e suppongo che tu stia usando un UITextView, quindi devi comunque rimuovere autoscroll = false altrimenti manterrà l'altezza e l'altezza relativa non agirà come previsto.
Eddwin Paz,

1
Questa è di gran lunga la soluzione più solida. Non preoccuparti estimatedRowHeight, influenza principalmente la dimensione della barra di scorrimento, mai l'altezza delle celle effettive. Sii audace nell'altezza che scegli: influenzerà l'animazione di inserimento / cancellazione.
SwiftArchitect,

Ecco il codice di esempio in swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

47

La soluzione proposta da @smileyborg è quasi perfetta. Se hai una cella personalizzata e ne desideri una o più UILabelcon altezze dinamiche, il metodo systemLayoutSizeFittingSize combinato con AutoLayout abilitato restituisce a a CGSizeZeromeno che non sposti tutti i vincoli della cella dalla cella al suo contenutoView (come suggerito da @TomSwift qui Come ridimensionare la superview a si adatta a tutte le sottoview con autolayout? ).

Per fare ciò è necessario inserire il seguente codice nella propria implementazione personalizzata UITableViewCell (grazie a @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];
    }
}

Mischiare la risposta di @smileyborg con questo dovrebbe funzionare.


systemLayoutSizeFittingSizedeve essere chiamato su contentView, non su cell
onmyway133

25

Un gotcha abbastanza importante che ho appena incontrato per pubblicare come risposta.

La risposta di @ smileyborg è per lo più corretta. Tuttavia, se si dispone di un codice nel layoutSubviewsmetodo della classe di celle personalizzata, ad esempio l'impostazione di preferredMaxLayoutWidth, non verrà eseguito con questo codice:

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

Mi ha confuso per un po '. Poi ho capito che è perché stanno solo innescando layoutSubviews sulla contentViewcella, non sulla cella stessa.

Il mio codice di lavoro è simile al seguente:

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

Nota che se stai creando una nuova cella, sono abbastanza sicuro che non hai bisogno di chiamare setNeedsLayout come dovrebbe essere già impostato. Nei casi in cui salvi un riferimento a una cella, dovresti probabilmente chiamarlo. In ogni caso non dovrebbe far male a niente.

Un altro suggerimento se si utilizzano sottoclassi di celle in cui si impostano cose come preferredMaxLayoutWidth. Come menziona @smileyborg, "la cella della vista della tabella non ha ancora avuto la larghezza fissata alla larghezza della vista della tabella". Questo è vero e problematico se stai facendo il tuo lavoro nella sottoclasse e non nel controller di visualizzazione. Tuttavia, puoi semplicemente impostare il frame della cella a questo punto usando la larghezza della tabella:

Ad esempio nel calcolo dell'altezza:

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

(Mi capita di memorizzare nella cache questa particolare cella per il riutilizzo, ma questo è irrilevante).



19

Finché il layout nella cella è buono.

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

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

Aggiornamento: è necessario utilizzare il ridimensionamento dinamico introdotto in iOS 8.


Questo sta lavorando per me su iOS7, è ora OK per chiamare tableView:cellForRowAtIndexPath:in tableView:heightForRowAtIndexPath:questo momento?
Formiche

Ok quindi questo non funziona, ma quando chiamo systemLayoutSizeFittingSize:in tableView:cellForRowAtIndexPath:e la cache il risultato quindi e quindi utilizzare tale in tableView:heightForRowAtIndexPath:funziona bene fino a quando i vincoli sono configurato correttamente, naturalmente!
Formiche

Funziona solo se usi dequeueReusableCellWithIdentifier: invece di dequeueReusableCellWithIdentifier: forIndexPath:
Antoine

1
Non credo davvero di chiamare tableView: cellForRowAtIndexPath: direttamente è un buon modo.
Itachi,

19

(per Xcode 8.x / Xcode 9.x leggi in fondo)

Fai attenzione al seguente problema in Xcode 7.x, che potrebbe essere fonte di confusione:

Interface Builder non gestisce correttamente la configurazione automatica delle celle di ridimensionamento. Anche se i tuoi vincoli sono assolutamente validi, IB si lamenterà comunque e ti darà suggerimenti ed errori confusi. Il motivo è che IB non è disposto a modificare l'altezza della riga in base ai vincoli (in modo che la cella si adatti al contenuto). Al contrario, mantiene fissa l'altezza della riga e inizia a suggerire di modificare i vincoli, che è necessario ignorare .

Ad esempio, immagina di aver impostato tutto correttamente, nessun avviso, nessun errore, tutto funziona.

inserisci qui la descrizione dell'immagine

Ora se cambi la dimensione del carattere (in questo esempio sto cambiando la dimensione del carattere dell'etichetta di descrizione da 17.0 a 18.0).

inserisci qui la descrizione dell'immagine

Poiché le dimensioni del carattere sono aumentate, l'etichetta ora vuole occupare 3 righe (prima occupava 2 righe).

Se Interface Builder funzionasse come previsto, ridimensionerebbe l'altezza della cella per adattarla alla nuova altezza dell'etichetta. Tuttavia, ciò che accade effettivamente è che IB visualizza l'icona rossa di errore di layout automatico e suggerisce di modificare le priorità di abbraccio / compressione.

inserisci qui la descrizione dell'immagine

È necessario ignorare questi avvisi. Quello che puoi * fare invece è cambiare manualmente l'altezza della riga in (seleziona Cella> Impostazioni dimensioni> Altezza riga).

inserisci qui la descrizione dell'immagine

Stavo modificando questa altezza un clic alla volta (utilizzando lo stepper su / giù) fino a quando gli errori con la freccia rossa scompaiono! (in realtà riceverai avvisi gialli, a quel punto vai avanti e fai 'aggiorna i frame', dovrebbe funzionare tutto).

* Nota che non devi effettivamente risolvere questi errori rossi o avvisi gialli in Interface Builder - in fase di esecuzione, tutto funzionerà correttamente (anche se IB mostra errori / avvisi). Assicurati solo che in fase di esecuzione nel registro della console non si verifichino errori di AutoLayout.

In effetti, cercare di aggiornare sempre l'altezza della riga in IB è molto fastidioso e talvolta vicino all'impossibile (a causa di valori frazionari).

Per evitare fastidiosi avvisi / errori IB, è possibile selezionare le viste interessate e scegliere Size Inspectorla proprietàAmbiguityVerify Position Only

inserisci qui la descrizione dell'immagine


Xcode 8.x / Xcode 9.x sembra (a volte) fare cose diversamente da Xcode 7.x, ma ancora in modo errato. Ad esempio, anche quando compression resistance priority/ hugging prioritysono impostati su richiesto (1000), Interface Builder potrebbe allungare o ritagliare un'etichetta per adattarla alla cella (anziché ridimensionare l'altezza della cella per adattarla all'etichetta). In tal caso, potrebbe anche non mostrare alcun avviso o errore di AutoLayout. O a volte fa esattamente quello che ha fatto Xcode 7.x, descritto sopra.


ciao è possibile dare l'altezza dinamica per la cella, che ha una vista tabella con cella con contenuto di cella dinamico.
Jignesh B,

18

Per impostare la quota automatica per l'altezza della riga e l'altezza della riga stimata, assicurarsi che le seguenti fasi siano efficaci, la dimensione automatica è efficace per il layout dell'altezza della cella / riga.

  • Assegna e implementa i dati di visualizzazione tabella Origine e delegato
  • Assegna UITableViewAutomaticDimensiona rowHeight e stimatoRowHeight
  • Implementare metodi delegate / dataSource (ovvero heightForRowAtrestituire un valore UITableViewAutomaticDimension)

-

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

Per l'istanza dell'etichetta in UITableviewCell

  • Imposta il numero di righe = 0 (& modalità di interruzione di riga = coda troncata)
  • Imposta tutti i vincoli (in alto, in basso, a destra a sinistra) rispetto al suo contenitore superview / cella.
  • Opzionale : impostare l'altezza minima per l'etichetta, se si desidera che l'area verticale minima sia coperta dall'etichetta, anche se non ci sono dati.

inserisci qui la descrizione dell'immagine

Nota : se hai più di una etichetta (UIElement) con lunghezza dinamica, che dovrebbe essere regolata in base alla sua dimensione del contenuto: Regola "Priorità di abbraccio e resistenza alla compressione del contenuto" per le etichette che desideri espandere / comprimere con priorità più alta.


1
Grazie mi sembra una soluzione chiara e semplice ma ho un problema che non sto usando etichette ma viste di testo, quindi ho bisogno dell'altezza della riga per aumentare man mano che vengono aggiunti i dati. Il mio problema è quindi di passare le informazioni ad heightForRowAt. Posso misurare l'altezza delle mie visualizzazioni di testo modificate ma ora devo modificare l'altezza della riga. Gradirei un po 'di aiuto per favore
Jeremy Andrews,

@JeremyAndrews Sicuramente ti aiuterà. Solleva la tua domanda con il codice sorgente che hai provato e i dettagli sul problema.
Krunal,

Funziona per me senza implementare l'origine tableView: heightForRowdati.
Hemang,

@Hemang - Da iOS 11+ funziona senza tableView: heightForRow. (per iOS 10- tableView: heightForRowè richiesto)
Krunal

1
@Hemang - La soluzione dipende dalla definizione esatta della query. Qui ho una soluzione comune generale per la tua query. Metti le condizioni dentro tableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
Krunal,

16

Come @ Bob-Spryn mi sono imbattuto in un gotcha abbastanza importante da pubblicare questo come risposta.

Ho lottato con la risposta di @ smileyborg per un po '. Il gotcha in cui mi sono imbattuto è se hai definito la tua cella prototipo in IB con elementi aggiuntivi ( UILabels, UIButtonsecc.) In IB quando crei un'istanza della cella con [ [YourTableViewCellClass alloc] init]non creerà un'istanza di tutti gli altri elementi all'interno di quella cella a meno che tu non abbia codice scritto per farlo. (Ho avuto un'esperienza simile coninitWithStyle .)

Per fare in modo che lo storyboard crei un'istanza di tutti gli elementi aggiuntivi ottieni la tua cella con [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](Non in [tableView dequeueReusableCellWithIdentifier:forIndexPath:]quanto ciò causerà problemi interessanti.) Quando lo fai, verranno istanziati tutti gli elementi definiti in IB.


15

Tabella dinamica Visualizza altezza cella e layout automatico

Un buon modo per risolvere il problema con lo layout automatico dello storyboard:

- (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
Questo è già stato ampiamente trattato nella risposta accettata a questa domanda.
smileyborg,

2
Sì, lo so ... ma non volevo usare PureLayout e il "trucco" dispatch_once mi ha aiutato molto, a risolverlo solo usando lo Storyboard.
Mohnasm,

13
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

inserisci qui la descrizione dell'immagine


13

Un'altra "soluzione": salta tutta questa frustrazione e usa invece un UIScrollView per ottenere un risultato che sembra identico a UITableView.

Questa è stata la "soluzione" dolorosa per me, dopo aver investito letteralmente oltre 20 ore molto frustranti nel tentativo di creare qualcosa di simile a quello che ha suggerito smileyborg e fallendo nel corso di molti mesi e tre versioni delle versioni di App Store.

La mia opinione è che se hai davvero bisogno del supporto di iOS 7 (per noi, è essenziale) la tecnologia è troppo fragile e ti toglierai i capelli provandoci. E che UITableView è in genere completo, a meno che tu non stia utilizzando alcune delle funzionalità avanzate di modifica delle righe e / o non abbia davvero bisogno di supportare più di 1000 "righe" (nella nostra app, in realtà non è mai più di 20 righe).

Il vantaggio aggiuntivo è che il codice diventa follemente semplice rispetto a tutte le cazzate dei delegati e avanti e indietro che viene fornito con UITableView. È solo un singolo ciclo di codice in viewOnLoad che sembra elegante ed è facile da gestire.

Ecco alcuni suggerimenti su come farlo:

  1. Utilizzando Storyboard o un file pennino, creare un ViewController e la vista radice associata.

  2. Trascina su un UIScrollView sulla vista principale.

  3. Aggiungi i vincoli superiore, inferiore, sinistro e destro alla vista di livello superiore in modo che UIScrollView riempia l'intera vista di radice.

  4. Aggiungi un UIView all'interno di UIScrollView e chiamalo "contenitore". Aggiungi i vincoli superiore, inferiore, sinistro e destro a UIScrollView (il suo genitore). TRUCCO CHIAVE: aggiungere anche un vincolo "Pari larghezze" per collegare UIScrollView e UIView.

    NOTA: verrà visualizzato un messaggio di errore "la vista di scorrimento ha un'altezza del contenuto scorrevole ambiguo" e che il contenitore UIView deve avere un'altezza di 0 pixel. Nessuno dei due errori sembra avere importanza quando l'app è in esecuzione.

  5. Crea file pennino e controller per ciascuna delle tue "celle". Usa UIView non UITableViewCell.

  6. Nel tuo ViewController root, aggiungi essenzialmente tutte le "righe" alla UIView del contenitore e aggiungi a livello di codice i vincoli che collegano i loro bordi sinistro e destro alla vista contenitore, i loro bordi superiori alla vista contenitore superiore (per il primo elemento) o precedente cellula. Quindi collegare la cella finale al fondo del contenitore.

Per noi, ogni "riga" è in un file pennino. Quindi il codice è simile al seguente:

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

Ed ecco il codice per 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))
    }
}

L'unico "gotcha" che ho trovato finora con questo approccio è che UITableView ha una bella caratteristica delle intestazioni di sezione "fluttuanti" nella parte superiore della vista mentre scorri. La soluzione di cui sopra non lo farà a meno che non si aggiunga ulteriore programmazione, ma per il nostro caso particolare questa funzionalità non era essenziale al 100% e nessuno se ne accorse quando scomparve.

Se desideri divisori tra le tue celle, aggiungi semplicemente una UIView alta 1 pixel nella parte inferiore della "cella" personalizzata che assomigli a un divisore.

Assicurati di attivare "rimbalzi" e "rimbalzo in verticale" per far funzionare il controllo di aggiornamento e quindi sembra più una vista da tavolo.

TableView mostra alcune righe vuote e divisori sotto il tuo contenuto, se non riempie lo schermo intero dove questa soluzione non lo fa. Ma personalmente preferisco che quelle file vuote non esistessero comunque - con un'altezza variabile delle celle mi sembrava sempre "buggy" avere le righe vuote lì dentro.

Spero che qualche altro programmatore legga il mio post PRIMA di perdere più di 20 ore cercando di capirlo con Table View nella loro app. :)


11

Ho dovuto usare le viste dinamiche (viste di impostazione e vincoli per codice) e quando volevo impostare la larghezza dell'etichetta preferitaMaxLayoutWidth era 0. Quindi ho un'altezza della cella errata.

Poi ho aggiunto

[cell layoutSubviews];

prima di eseguire

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

Dopo che la larghezza dell'etichetta era come previsto e l'altezza dinamica stava calcolando a destra.


9

Supponiamo che tu abbia una cella con una sottoview e desideri che l'altezza della cella sia abbastanza alta da comprendere la subview + il riempimento.

1) Impostare il vincolo inferiore della sottoview uguale a cell.contentView meno il padding desiderato. Non impostare vincoli sulla cella o cell.contentView stesso.

2) Impostare la rowHeightproprietà di tableView o tableView:heightForRowAtIndexPath:su UITableViewAutomaticDimension.

3) Impostare la estimatedRowHeightproprietà di tableView otableView:estimatedHeightForRowAtIndexPath: su una migliore ipotesi dell'altezza.

Questo è tutto.


7

Se esegui il layout a livello di codice, ecco cosa considerare per iOS 10 utilizzando le ancore in Swift.

Esistono tre regole / passaggi

NUMERO 1: imposta queste due proprietà di tableview su viewDidLoad, il primo dice alla tableview che dovrebbero aspettarsi dimensioni dinamiche sulle loro celle, il secondo è solo per consentire all'app di calcolare la dimensione dell'indicatore della barra di scorrimento, quindi aiuta per prestazione.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMERO 2: è importante aggiungere le visualizzazioni secondarie al contenuto Visualizza della cella non alla vista e utilizzare anche il layout layout per ancorare le visualizzazioni secondarie in alto e in basso, questo è un esempio pratico di come farlo.

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)

        ])
}

Crea un metodo che aggiungerà le visualizzazioni secondarie ed eseguirà il layout, chiamandolo nel metodo init.

NUMERO 3: NON CHIAMARE IL METODO:

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

Se lo fai, annullerai la tua implementazione.

Segui queste 3 regole per le celle dinamiche in visualizzazioni di tabella.

ecco un'implementazione funzionante https://github.com/jamesrochabrun/MinimalViewController


in Swift 5 UITableViewAutomaticDimension rinominato in UITableView.automaticDimension
James Rochabrun

4

Se hai una stringa lunga . ad esempio uno che non ha un'interruzione di linea. Quindi potresti incorrere in alcuni problemi.

La correzione "presunta" è menzionata dalla risposta accettata e da poche altre risposte. Devi solo aggiungere

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Trovo la risposta di Suragh il più completo e conciso , quindi non confondere.

Anche se non spiega perché sono necessari questi cambiamenti. Facciamolo.

Rilascia il seguente codice in un progetto.

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

Si noti che non ho aggiunto alcuna dimensione vincolo di . Ho aggiunto solo i vincoli centerX, centerY. Ma l'etichetta verrà comunque dimensionata correttamente Perché?

Per via di contentSize.

Per elaborare meglio questo, prima segui il passaggio 0, quindi commenta i passaggi 1-6. Lascia setupLayout()stare. Osserva il comportamento.

Quindi decommenta il passaggio 1 e osserva.

Quindi decommenta il passaggio 2 e osserva.

Fallo fino a quando non hai decommentato tutti i 6 passaggi e osservato i loro comportamenti.

Cosa può concludere da tutto ciò? Quali fattori possono cambiare il contenSize?

  1. Lunghezza del testo: se hai un testo più lungo, la larghezza di intrinsicContentSize aumenterà
  2. Interruzioni di riga: se aggiungi, \nla larghezza di intrinsicContentSize sarà la larghezza massima di tutte le linee. Se una riga ha 25 caratteri, un'altra ha 2 caratteri e un'altra ha 21 caratteri, la larghezza verrà calcolata in base ai 25 caratteri
  3. Numero di righe consentite: devi impostare numberOfLinessu 0altrimenti non avrai più righe. Il tuo numberOfLinesregolerà del intrinsicContentSize altezza
  4. Apportare modifiche: immagina che in base al tuo testo, la larghezza intrinseca del tuo formato fosse 200e l'altezza fosse 100, ma volevi limitare la larghezza al contenitore dell'etichetta che cosa hai intenzione di fare? La soluzione è impostarla sulla larghezza desiderata. Si farlo impostando preferredMaxLayoutWidthad 130allora la vostra nuova intrinsicContentSize avrà una larghezza di circa 130. L'altezza sarebbe ovviamente più che 100perché avresti bisogno di più linee. Detto questo se i tuoi vincoli sono impostati correttamente, non dovrai assolutamente usarli! Per ulteriori informazioni, vedi questa risposta e i suoi commenti. Devi solo usarlo preferredMaxLayoutWidthse non hai vincoli che limitano la larghezza / altezza come in uno si potrebbe dire "non avvolgere il testo a meno che non superi ilpreferredMaxLayoutWidth ". Ma con certezza al 100% se imposti il ​​comando iniziale / finale e numberOfLinessu0 poi sei bravo!Per farla breve, la maggior parte delle risposte qui che raccomandano di usarlo sono SBAGLIATE! Non ne hai bisogno. La necessità è un segno che i tuoi vincoli non sono impostati correttamente o che non hai vincoli

  5. Dimensione carattere: nota anche che se aumenti fontSize aumenterà l' altezza intrinsicContentSize . Non l'ho mostrato nel mio codice. Puoi provarlo da solo.

Quindi torniamo al tuo esempio tableViewCell:

Tutto quello che devi fare è:

  • imposta il numberOfLinesa0
  • vincolare correttamente l'etichetta ai margini / bordi
  • Non è necessario impostare preferredMaxLayoutWidth.

1

Nel mio caso, devo creare una cella personalizzata con un'immagine che proviene dal server e può avere qualsiasi larghezza e altezza. E due UILabel con dimensioni dinamiche (sia larghezza che altezza)

ho ottenuto lo stesso qui nella mia risposta con autolayout e programmaticamente:

Fondamentalmente sopra la risposta di @smileyBorg mi ha aiutato ma systemLayoutSizeFittingSize non ha mai funzionato per me, nel mio approccio:

1. Nessun uso della proprietà di calcolo automatico dell'altezza della riga. 2.Non utilizzare l'altezza stimata 3.Non sono necessari aggiornamenti inutili Vincoli. 4.Non utilizzare la larghezza massima automatica preferita del layout. 5. Nessun uso di systemLayoutSizeFittingSize (dovrebbe essere utile ma non funzionante per me, non so cosa sta facendo internamente), ma invece il mio metodo - (float) getViewHeight funziona e so cosa sta facendo internamente.

È possibile avere altezze diverse in una cella UITableView quando utilizzo diversi modi di visualizzare la cella?


1

Nel mio caso, l'imbottitura era dovuta alle altezze di sectionHeader e sectionFooter, dove lo storyboard mi permetteva di cambiarlo al minimo 1. Quindi in viewDidLoad metodo:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

1

Ho appena fatto qualche stupido tentativo ed errore con i 2 valori di rowHeighte estimatedRowHeightho pensato che potesse fornire alcune informazioni di debug:

Se le impostate entrambe OPPURE impostate solo l' estimatedRowHeightopzione otterrete il comportamento desiderato:

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

Si suggerisce di fare del proprio meglio per ottenere la stima corretta, ma il risultato finale non è diverso. Interesserà solo le tue prestazioni.

inserisci qui la descrizione dell'immagine


Se imposti solo rowHeight, ovvero esegui solo:

tableView.rowHeight = UITableViewAutomaticDimension

il risultato finale non sarebbe come desiderato:

inserisci qui la descrizione dell'immagine


Se si imposta estimatedRowHeightsu 1 o su un valore inferiore, si arresta in modo anomalo indipendentemente da rowHeight.

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

Mi sono bloccato con il seguente messaggio di errore:

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

Per quanto riguarda la risposta accettata da @smileyborg, ho trovato

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

essere inaffidabili in alcuni casi in cui i vincoli sono ambigui. Meglio forzare il motore di layout a calcolare l'altezza in una direzione, usando la categoria helper su UIView di seguito:

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

Dove w: è la larghezza della vista tabella


0

Aggiungi semplicemente queste due funzioni nel tuo viewcontroller per risolvere il tuo problema. Qui, list è un array di stringhe che contiene la stringa di ogni riga.

 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

Se l'altezza della cella è dinamica in base al contenuto, è necessario contarla con precisione e quindi restituire il valore dell'altezza prima del rendering della cella. Un modo semplice è definire il metodo di conteggio nel codice della cella della vista tabella per il controller da chiamare al metodo delegato dell'altezza della cella della tabella. Non dimenticare di contare la larghezza della cornice della cella reale (il valore predefinito è 320) se l'altezza dipende dalla larghezza della tabella o dello schermo. Cioè, nel metodo delegato dell'altezza della cella della tabella, utilizzare cell.frame per correggere prima la larghezza della cella, quindi chiamare il metodo dell'altezza di conteggio definito nella cella per ottenere il valore adatto e restituirlo .

PS. Il codice per la generazione di un oggetto cella potrebbe essere definito in un altro metodo che può essere chiamato da un diverso metodo del delegato di cella vista tabella.


-3

UITableView.automaticDimension può essere impostato tramite Interface Builder:

Xcode> Storyboard> Impostazioni dimensioni

Cella di visualizzazione tabella> Altezza riga> Automatico

Ispettore dimensioni


-4

ancora un'altra soluzione iOs7 + iOs8 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
    }
}

nota: systemLayoutSizeFittingSize non funziona nel mio caso
djdance

l'altezza della cella non è corretta cellForRowAtIndexPath, la cella non è ancora strutturata a questo punto.
Andrii Chernenko,

in iOs7 è un valore fisso, cioè funziona. È possibile impostare all'esterno cellForRowAtIndexPath se si desidera
djdance,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.