Comment utiliser AutoLayout dans UITableViewCell pour avoir des mises en page de cellule dynamiques et des hauteurs de ligne variables ?

Le mécanisme d'Autolayout est utilisé dans une application iOS pour permettre à une vue de s'adapter à la taille d'écran utilisée pour l'affichage. Une application gère ainsi des périphériques ayant une taille d'écran variée (iPhone, iPad). Pour intégrer ce mécanisme avec la classe UITableView, vous devez suivre plusieurs étapes, et ces étapes varient selon que vous développez une application compatible avec iOS7 ou bien iOS8.

La première étape utilise le langage Visual Format. Il faut ajouter des contraintes sur chaque cellule de type UITableViewCell pour qu'elles aient leurs bords fixés à l'élément contentView de la cellule (et non pas à la cellule en elle-même). La priorité est la connexion au niveau des bords en haut et en bas, afin que la cellule s'adapte verticalement à son contenu. Il est important de s'assurer également que les contraintes content compression resistance et content hugging, pour la dimension verticale de la cellule, ne soient pas écrasées par d'autres contraintes d'une priorité plus élevée. Cette étape est cruciale : si les contraintes ne sont pas correctement mises en place, l'autolayout ne fonctionnera pas.
Si vous rencontrez des difficultés pour mettre en place les contraintes avec le langage Visual Format, vous pouvez les modifier directement dans le code de l'application. Il faut pour cela ajouter les contraintes dans la méthode updateConstraints pour chaque élément de type UITableViewCell. Cette méthode peut être appelée plusieurs fois dans le code, donc, il est préférable d'utiliser un booléen avec une condition pour n'ajouter les contraintes qu'une seule fois.

La deuxième étape est l'attribution des valeurs de l'attribut reuseIdentifier. Pour chaque lot de contraintes lié à une disposition, il doit être unique. Si, par exemple, votre formulaire utilise plusieurs dispositions en affichant un ou plusieurs éléments sous certaines conditions, il est nécessaire d'utiliser une valeur reuseIdentifier par agencement du formulaire. L'attribution de la valeur reuseIdentifier doit se faire une fois que la cellule est initialisée et que les contraintes sont mises en place.
Il ne faut pas intégrer une cellule avec des contraintes différentes dans un groupe de cellules (qui ont déjà leur contrainte et la même valeur pour l'attribut reuseIdentifier) pour ensuite adapter les contraintes. Le moteur qui gère la disposition n'est pas conçu pour gérer une modification importante des contraintes, votre application aura des problèmes de performance.

Si vous développez une application pour iOS8, il ne reste que 2 instructions à ajouter pour activer le calcul automatique de la hauteur des cellules. La première directive consiste à activer le calcul automatique en affectant à la valeur rowHeight la constante UITableViewAutomaticDimension.
self.tableView.rowHeight = UITableViewAutomaticDimension;
Pour que le calcul fonctionne, il faut ensuite donner une estimation de la hauteur moyenne des cellules. Cette estimation n'a pas besoin d'être précise. Pour déterminer la taille d'affichage, la tableView va demander à chaque cellule la hauteur dont elle a besoin pour afficher son contenu. Une fois la hauteur déterminée, l'estimation que vous avez indiquée sera mise à jour avec la valeur calculée.
self.tableView.estimatedRowHeight = 30.0;
Si vous avez une trop grande différence entre les hauteurs des cellules de votre tableView, il peut se produire un problème lors du défilement dans votre application. La barre de défilement peut sauter d'un emplacement à un autre et provoquer ainsi des problèmes d'affichage. Si ce cas se produit, vous devrez implémenter la méthode tableView:estimatedHeightForRowAtIndexPath: qui permet d'effectuer les calculs minimum pour retourner une estimation plus précise pour chaque ligne.

SI vous développez une application pour iOS7, il faudra écrire davantage de développements, car le kit de développement ne gère pas l'autolayout.
Vous devez d'abord instancier une cellule (classe UITableViewCell) qui ne sera pas affichée à l'écran. Elle sera stockée dans une variable et ne sera pas retournée par la méthode tableView:cellForRowAtIndexPath: qui est utilisée pour l'affichage de la vue. Configurez ensuite la cellule pour qu'elle contienne tous les éléments qu'elle devrait contenir si elle était affichée à l'écran. Forcez la vue à disposer tous les éléments pour l'affichage et utilisez la méthode systemLayoutSizeFittingSize: de l'attribut contentView de votre cellule pour trouver la hauteur. Si vous voulez la taille minimum, utilisez la méthode UILayoutFittingCompressedSize. Toutes ces directives doivent être implémentées dans la méthode déléguée tableView:heightForRowAtIndexPath: :
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// reuse identifier utilisé par la cellule.
NSString *reuseIdentifier = ...;
// Créez un dictionnaire pour y stocker une cellule de type offscreenCells (elle ne sera pas affichée à l'écran)
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configurez ensuite la cellule en y ajoutant les éléments à afficher:
// cell.textLabel.text = texte affiché dans la cellule;
// Utilisez les lignes suivantes pour ajouter les contraintes que vous avez créées auparavant sur cette cellule
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Modifiez la largeur de la cellule pour qu'elle soit égale à celle de la TableView
// Si vous développez un cas spécifique avec, par exemple, un affichage uniquement à un endroit de l'écran, utilisez cette largeur.
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Ces directives forcent la cellule à ajuster sa disposition en fonction du contenu
[cell setNeedsLayout];
[cell layoutIfNeeded];
// On récupère la hauteur de la cellule
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// On ajoute un point de distance en plus pour prendre en compte la marge entre le bas du contenu de la cellule (contentView) et le bas de la cellule.
height += 1.0f;
// On retourne la hauteur
return height;
}
L'étape suivante dans le développement pour iOS7 est la même que la deuxième étape du développement pour iOS8 : il faut mettre en place une estimation de la hauteur de vos cellules. Il faut utiliser les mêmes méthodes qu'avec iOS8.
Si jamais votre application est lente, vous devrez stocker la hauteur calculée pour l'autolayout dans une constante. De cette manière, la hauteur ne sera calculée qu'une seule fois et, à chaque nouvel appel, la constante sera utilisée. Si vous utilisez cette solution, gardez bien à l'esprit qu'il faudra modifier la constante si le contenu de la cellule est modifié (ajout d'un élément, changement de la taille de la police, etc.).

Divers