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 UITableViewCell
sottoclasse, 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:
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
updateConstraints
metodo della sottoclasse UITableViewCell. Nota che updateConstraints
può 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 updateConstraints
in 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 constant
proprietà su alcuni vincoli), posizionarlo all'interno updateConstraints
ma al di fuori del controllo per didSetupConstraints
consentirne 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 rowHeight
proprietà nella vista tabella sulla costante UITableViewAutomaticDimension
. Quindi, devi semplicemente abilitare la stima dell'altezza della riga impostando la estimatedRowHeight
proprietà 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 contentView
deve 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 estimatedRowHeight
proprietà nella vista tabella (in viewDidLoad
o 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" contentView
per scoprire qual è l'altezza richiesta della cella. Utilizzare UILayoutFittingCompressedSize
per 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 estimatedRowHeight
proprietà 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 estimatedRowHeight
proprietà nella vista tabella (in viewDidLoad
o 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 .