TL; DR: Vous n'aimez pas lire? Accédez directement aux exemples de projets sur GitHub:
Description conceptuelle
Les 2 premières étapes ci-dessous sont applicables quelles que soient les versions d'iOS pour lesquelles vous développez.
1. Configurer et ajouter des contraintes
Dans votre UITableViewCell
sous-classe, ajoutez des contraintes de sorte que les sous-vues de la cellule aient leurs bords épinglés aux bords de contentView de la cellule (surtout aux bords supérieur ET inférieur). REMARQUE: n'épinglez pas les sous-vues à la cellule elle-même; seulement aux cellules contentView
! Laissez la taille de contenu intrinsèque de ces sous-vues déterminer la hauteur de la vue de contenu de la cellule de vue tableau en vous assurant que la résistance à la compression de contenu et les contraintes de contournement de contenu dans la dimension verticale de chaque sous-vue ne sont pas remplacées par les contraintes de priorité plus élevée que vous avez ajoutées. ( Hein? Cliquez ici. )
N'oubliez pas que l'idée est d'avoir les sous-vues de la cellule connectées verticalement à la vue de contenu de la cellule afin qu'elles puissent «exercer une pression» et étendre la vue de contenu pour les adapter. En utilisant un exemple de cellule avec quelques sous-vues, voici une illustration visuelle de ce à quoi certaines (pas toutes!) De vos contraintes devraient ressembler:
Vous pouvez imaginer que plus de texte est ajouté à l'étiquette de corps multiligne dans l'exemple de cellule ci-dessus, il devra s'agrandir verticalement pour s'adapter au texte, ce qui forcera effectivement la cellule à augmenter en hauteur. (Bien sûr, vous devez obtenir les bonnes contraintes pour que cela fonctionne correctement!)
Obtenir vos bonnes contraintes est certainement la partie la plus difficile et la plus importante pour obtenir des hauteurs de cellule dynamiques avec Auto Layout. Si vous faites une erreur ici, cela pourrait empêcher tout le reste de fonctionner - alors prenez votre temps! Je recommande de configurer vos contraintes dans le code car vous savez exactement quelles contraintes sont ajoutées où, et il est beaucoup plus facile de déboguer en cas de problème. L'ajout de contraintes dans le code peut être aussi simple et nettement plus puissant que Interface Builder utilisant des ancres de mise en page ou l'une des fantastiques API open source disponibles sur GitHub.
- Si vous ajoutez des contraintes dans le code, vous devez le faire une fois depuis la
updateConstraints
méthode de votre sous-classe UITableViewCell. Notez que cela updateConstraints
peut être appelé plus d'une fois, donc pour éviter d'ajouter les mêmes contraintes plus d'une fois, assurez-vous d'encapsuler votre code d'ajout de contrainte updateConstraints
dans une vérification pour une propriété booléenne telle que didSetupConstraints
(que vous définissez sur YES après avoir exécuté votre contrainte -ajout de code une fois). En revanche, si vous disposez d'un code qui met à jour les contraintes existantes (telles que l'ajustement de la constant
propriété sur certaines contraintes), placez-le dans updateConstraints
mais en dehors de la vérification pour didSetupConstraints
qu'il puisse s'exécuter à chaque appel de la méthode.
2. Déterminer les identificateurs uniques de réutilisation des cellules de vue tabulaire
Pour chaque ensemble unique de contraintes dans la cellule, utilisez un identifiant de réutilisation de cellule unique. En d'autres termes, si vos cellules ont plus d'une disposition unique, chaque disposition unique doit recevoir son propre identifiant de réutilisation. (Un bon indice que vous devez utiliser un nouvel identifiant de réutilisation est lorsque votre variante de cellule a un nombre différent de sous-vues ou que les sous-vues sont organisées de manière distincte.)
Par exemple, si vous affichez un e-mail dans chaque cellule, vous pouvez avoir 4 mises en page uniques: messages avec uniquement un objet, messages avec un objet et un corps, messages avec un objet et une pièce jointe photo et messages avec un objet, corps et photo. Chaque disposition a des contraintes complètement différentes requises pour y parvenir, donc une fois que la cellule est initialisée et que les contraintes sont ajoutées pour l'un de ces types de cellules, la cellule doit obtenir un identifiant de réutilisation unique spécifique à ce type de cellule. Cela signifie que lorsque vous retirez une cellule de la réutilisation, les contraintes ont déjà été ajoutées et sont prêtes à être utilisées pour ce type de cellule.
Notez qu'en raison des différences de taille de contenu intrinsèque, les cellules avec les mêmes contraintes (type) peuvent toujours avoir des hauteurs différentes! Ne confondez pas des dispositions fondamentalement différentes (différentes contraintes) avec différents cadres de vue calculés (résolus à partir de contraintes identiques) en raison de différentes tailles de contenu.
- N'ajoutez pas de cellules avec des ensembles de contraintes complètement différents au même pool de réutilisation (c'est-à-dire utilisez le même identifiant de réutilisation), puis essayez de supprimer les anciennes contraintes et de configurer de nouvelles contraintes à partir de zéro après chaque retrait de file d'attente. Le moteur de mise en page automatique interne n'est pas conçu pour gérer les changements de contraintes à grande échelle, et vous verrez d'énormes problèmes de performances.
Pour iOS 8 - Cellules à dimensionnement automatique
3. Activer l'estimation de la hauteur de ligne
Pour activer les cellules de vue de tableau à dimensionnement automatique, vous devez définir la propriété rowHeight de la vue de tableau sur UITableViewAutomaticDimension. Vous devez également affecter une valeur à la propriété estimationRowHeight. Dès que ces deux propriétés sont définies, le système utilise la disposition automatique pour calculer la hauteur réelle de la ligne
Apple: Utilisation de cellules d'affichage de table à dimensionnement automatique
Avec iOS 8, Apple a internalisé une grande partie du travail que vous deviez auparavant implémenter avant iOS 8. Pour permettre au mécanisme de cellule à dimensionnement automatique de fonctionner, vous devez d'abord définir la rowHeight
propriété de la vue de table sur la constante UITableViewAutomaticDimension
. Ensuite, vous devez simplement activer l'estimation de la hauteur de ligne en définissant la estimatedRowHeight
propriété de la vue de table sur une valeur différente de zéro, par exemple:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
Cela permet de fournir à la vue de table une estimation / espace réservé temporaire pour les hauteurs de ligne des cellules qui ne sont pas encore à l'écran. Ensuite, lorsque ces cellules sont sur le point de défiler à l'écran, la hauteur réelle de la ligne sera calculée. Pour déterminer la hauteur réelle de chaque ligne, la vue de tableau demande automatiquement à chaque cellule quelle hauteur elle contentView
doit être basée sur la largeur fixe connue de la vue de contenu (qui est basée sur la largeur de la vue de table, moins tout élément supplémentaire comme un index de section ou vue accessoire) et les contraintes de disposition automatique que vous avez ajoutées à la vue de contenu et aux sous-vues de la cellule. Une fois cette hauteur de cellule réelle déterminée, l'ancienne hauteur estimée de la ligne est mise à jour avec la nouvelle hauteur réelle (et tous les ajustements apportés à contentSize / contentOffset de la vue tableau sont effectués selon vos besoins).
D'une manière générale, l'estimation que vous fournissez n'a pas besoin d'être très précise - elle est uniquement utilisée pour dimensionner correctement l'indicateur de défilement dans la vue tableau, et la vue tableau fait un bon travail d'ajustement de l'indicateur de défilement pour les estimations incorrectes lorsque vous faites défiler les cellules à l'écran. Vous devez définir la estimatedRowHeight
propriété sur la vue de table (dans viewDidLoad
ou similaire) sur une valeur constante qui est la hauteur de ligne "moyenne". Ce n'est que si la hauteur de vos lignes présente une variabilité extrême (par exemple, diffère d'un ordre de grandeur) et que vous remarquez l'indicateur de défilement "sauter" lorsque vous faites défiler si vous vous donnez la peine de mettre tableView:estimatedHeightForRowAtIndexPath:
en œuvre le calcul minimal requis pour renvoyer une estimation plus précise pour chaque ligne.
Pour la prise en charge iOS 7 (implémentation du dimensionnement automatique des cellules vous-même)
3. Faites une passe de mise en page et obtenez la hauteur de la cellule
Tout d'abord, instanciez une instance hors écran d'une cellule de vue de table, une instance pour chaque identifiant de réutilisation , qui est utilisée strictement pour les calculs de hauteur. (Hors écran, ce qui signifie que la référence de cellule est stockée dans une propriété / ivar sur le contrôleur de vue et n'est jamais retournée tableView:cellForRowAtIndexPath:
pour que la vue de table s'affiche réellement à l'écran.) Ensuite, la cellule doit être configurée avec le contenu exact (par exemple, texte, images, etc.) qu'il tiendrait s'il devait être affiché dans la vue de table.
Ensuite, la force de la cellule immédiatement présentation de ses sous - vues, puis utilisez la systemLayoutSizeFittingSize:
méthode sur le UITableViewCell
« s contentView
pour savoir ce que la hauteur requise de la cellule est. Utilisez UILayoutFittingCompressedSize
pour obtenir la plus petite taille requise pour s'adapter à tout le contenu de la cellule. La hauteur peut ensuite être renvoyée par la tableView:heightForRowAtIndexPath:
méthode déléguée.
4. Utiliser les hauteurs de ligne estimées
Si votre vue de table contient plus de quelques dizaines de lignes, vous constaterez que l'exécution de la résolution automatique des contraintes de mise en page peut rapidement embourber le thread principal lors du premier chargement de la vue de table, comme cela tableView:heightForRowAtIndexPath:
est appelé sur chaque ligne lors du premier chargement ( afin de calculer la taille de l'indicateur de défilement).
Depuis iOS 7, vous pouvez (et devez absolument) utiliser la estimatedRowHeight
propriété dans la vue de table. Cela permet de fournir à la vue de table une estimation / un espace réservé temporaire pour les hauteurs de ligne des cellules qui ne sont pas encore à l'écran. Ensuite, lorsque ces cellules sont sur le point de défiler à l'écran, la hauteur réelle de la ligne sera calculée (en appelant tableView:heightForRowAtIndexPath:
) et la hauteur estimée mise à jour avec celle réelle.
D'une manière générale, l'estimation que vous fournissez n'a pas besoin d'être très précise - elle est uniquement utilisée pour dimensionner correctement l'indicateur de défilement dans la vue tableau, et la vue tableau fait un bon travail d'ajustement de l'indicateur de défilement pour les estimations incorrectes lorsque vous faites défiler les cellules à l'écran. Vous devez définir la estimatedRowHeight
propriété sur la vue de table (dans viewDidLoad
ou similaire) sur une valeur constante qui est la hauteur de ligne "moyenne". Ce n'est que si la hauteur de vos lignes présente une variabilité extrême (par exemple, diffère d'un ordre de grandeur) et que vous remarquez l'indicateur de défilement "sauter" lorsque vous faites défiler si vous vous donnez la peine de mettre tableView:estimatedHeightForRowAtIndexPath:
en œuvre le calcul minimal requis pour renvoyer une estimation plus précise pour chaque ligne.
5. (Si nécessaire) Ajouter un cache de hauteur de ligne
Si vous avez fait tout ce qui précède et que vous trouvez toujours que les performances sont inacceptablement lentes lors de la résolution des contraintes tableView:heightForRowAtIndexPath:
, vous devrez malheureusement implémenter une mise en cache pour les hauteurs de cellule. (C'est l'approche suggérée par les ingénieurs d'Apple.) L'idée générale est de laisser le moteur de mise en page automatique résoudre les contraintes la première fois, puis de mettre en cache la hauteur calculée pour cette cellule et d'utiliser la valeur mise en cache pour toutes les demandes futures pour la hauteur de cette cellule. L'astuce est bien sûr de vous assurer que vous effacez la hauteur mise en cache pour une cellule lorsque quelque chose se produit qui pourrait modifier la hauteur de la cellule - principalement, ce serait lorsque le contenu de cette cellule change ou lorsque d'autres événements importants se produisent (comme l'ajustement de l'utilisateur le curseur de taille de texte Dynamic Type).
Exemple de code générique iOS 7 (avec beaucoup de commentaires juteux)
- (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;
}
}
Exemples de projets
Ces projets sont des exemples pleinement opérationnels de vues de table avec des hauteurs de ligne variables en raison de cellules de vue de table contenant du contenu dynamique dans UILabels.
Xamarin (C # /. NET)
Si vous utilisez Xamarin, consultez cet exemple de projet mis en place par @KentBoogaart .