Accueil Nos publications Blog Les mystères d’AutoLayout

Les mystères d’AutoLayout

Apple-WWDC-2015-LogoA la WWDC 2015, qui s’est déroulée au mois de juin, Apple a annoncé des nouveautés comme iOS 9, Apple Music, ou encore OS X « El Capitan ». Voilà la majeure partie de ce que la plupart des gens auront retenu cette année. Mais pour les développeurs, cet événement aura aussi été une vraie mine d’or en termes de conférences techniques, et pour faire suite à mon précédent article sur les bases du storyboard, nous allons rester sur le même sujet et passer au niveau supérieur.

Accrochez vos ceintures, car dans cet article nous allons lever le voile sur certains mystères liés aux interfaces utilisateurs sous iOS, dont une grande partie a été traitée lors de cette WWDC 2015. Les nouveautés et astuces abordées, qui sont pour certaines cocasses, peuvent apporter une aide conséquente pour les développeurs qui travaillent avec Interface Builder ou directement depuis le code, qu’ils soient débutants ou plus expérimentés.

Mise en gardexcodebeta

Attention, cet article parle de composants ou systèmes qui sont disponibles uniquement avec la version 7.0 de Xcode. Si vous n’avez pas fait la mise à jour, ne tentez pas de faire la même chose à la maison ! Aucun danger mais juste une déception à la clé.

Petit rappel sur les bases

Pour bien gérer les vues du storyboard, et adapter l’interface aux iPhones et iPads, il est indispensable d’utiliser Auto Layout et les size class. Ce système fonctionne avec des contraintes, qui vont positionner les éléments entre eux ou avec les bords de l’écran, et qui sont activables ou désactivables à souhait, en fonction du type d’écran visé ou de l’orientation. Le développeur a juste à définir les contraintes comme il veut, et c’est lors de la compilation que la magie va opérer et que le Layout Engine va rendre l’effet voulu. Dit comme ça, cela paraît simple et magique, mais non. Il faut quand même comprendre correctement la logique des contraintes, sinon la conception d’interfaces adaptatives peut devenir la cause d’une apparition de cheveux blancs, et personne n’aime ça.

pmzd6

Et quand bien même on peut arriver à dompter Auto Layout, le système actuel rend la gestion des vues parfois assez illisible. Heureusement, Apple arrive à notre rescousse !

 

Un problème pour aligner des éléments ? Pas de panique, iOS 9 arrive, et la StackView aussi !

L’alignement d’éléments (vertical ou horizontal) est une vraie plaie pour ceux qui ont déjà expérimenté cela avec le storyboard. Ici, aucun rapport avec le StackPanel en Xaml, il faut s’embêter avec un nombre assez conséquent de contraintes pour arriver à l’effet voulu. Du coup, lors de débats pour comparer le développement d’interfaces sous iOS, Android et Windows Phone, le point de l’alignement en particulier décrédibilise un peu iOS par rapport aux autres (surtout Windows Phone). Mais cette époque est révolue ; bientôt, iOS 9 apportera avec lui la StackView. Comme son nom l’indique, ce nouveau type de vue (qui vient compléter la TableView et la Collectionview) va permettre l’empilement des éléments qu’elle contient, à la verticale ou à l’horizontale. Le premier effet kiss cool : grâce à la StackView, on réduit le nombre de contraintes et donc la complexité de la vue, car c’est elle qui va gérer comme une grande (ou presque) ses éléments enfants. Le deuxième effet kiss cool : la StackView va également régler des problèmes de maintenabilité des vues avec AutoLayout puisque, par exemple, l’insertion d’un nouvel élément se fera beaucoup plus facilement (on le place au-dessus ou en dessous d’un autre, ou à gauche ou à droite).

Les possibilités de placement avec la StackView sont les suivantes :

  • à l’horizontale : Top, Center, Bottom, Baseline
  • à la verticale : Fill, Leading (collé à gauche), Center, Trailing (collé à droite)

Mais Apple ne s’arrête pas là. Il est en effet aussi possible de gérer la distribution des éléments dans la StackView :

  • Fill : les éléments vont remplir la totalité de la vue, selon leur taille de base
  • Fill equally : les éléments vont remplir la vue avec des tailles égales
  • Fill proportionnally : le remplissage la vue sera basé sur la taille des sous vues, tout en s’adaptant proportionnellement
  • Equal spacing : les éléments vont se positionner entre eux avec un espace égal

Capture d’écran 2015-08-17 à 13.32.28

 

Bien évidemment, dans le cas d’éléments cachés ou non, la StackView va adapter sa taille, ce qui va permettre de gérer facilement une visibilité dynamique d’éléments graphiques, sans se prendre la tête avec beaucoup de contraintes.

KMT8-LOGO

 

Du coup, pour tout développeur iOS qui se respecte, l’annonce de la StackView a été équivalente à la découverte du Graal (j’exagère à peine ! Le conférencier lui-même était exalté en l’annonçant).

 

 

En revanche, il fallait s’en douter, la StackView n’est pas vraiment magique, et demande un petit temps d’adaptation, surtout si on veut jouer avec la distribution des éléments. En voici la preuve.

J’ai en effet pu tester le comportement de la StackView, disponible avec la bêta de Xcode 7, et je me suis rendue compte que selon le type de distribution et le contenu, on ne pouvait pas s’en sortir sans contrainte. Dans mon exemple ci-dessous, la StackView arrive à s’adapter en mode Fill Equally (1ère image) et Fill Proportionnaly (2ème image), mais réagit mal en mode Fill et Equal Spacing (3ème image pour les deux cas). Les images n’ont aucune contrainte de hauteur, mais même si on en rajoute, cela n’arrange pas vraiment l’histoire.

Capture d’écran 2015-08-17 à 13.33.35Capture d’écran 2015-08-17 à 13.33.20Capture d’écran 2015-08-25 à 13.55.14

Du coup pour l’instant le rendu n’est pas terrible, mais nous reviendrons très vite là-dessus.

La gestion des contraintes selon la taille d’écran ou l’orientation

On l’avait vu dans l’article précédent, Interface Builder offre la possibilité d’activer ou désactiver des contraintes, pour satisfaire le positionnement des éléments en fonction de la Size Class (donc de la taille d’écran ou de l’orientation de l’appareil).

Petit rappel, la procédure est la suivante : on définit tout d’abord les contraintes en mode Any Any, puis on passe dans une autre Size Class (disons Regular Regular). Après avoir effectué les modifications voulues sur la place des éléments, on ajoute les nouvelles contraintes. Ensuite, on accède au détail de la contrainte que l’on souhaite désactiver dans la Size Class courante, on choisit la Size Class courante, et on décoche la checkbox qui vient de se rajouter. Et voilà, le tour est joué, la contrainte est activée en Any Any, c’est à dire sur toutes les Size Class, sauf en Regular Regular.

Pour les amoureux du code, il est aussi possible d’y activer ou désactiver les contraintes ! Et en bonus, cela permet de créer des animations. Je ne vais pas détailler ici comment cela fonctionne, mais il est utile de savoir que c’est possible, sans trop de difficulté, sachant que des animations donnent un réel plus à une application.

Vous pourrez retrouver le projet exemple fourni avec la vidéo de la conférence lors de la WWDC sur le lien suivant.

 

Les priorités de contraintes, pour gérer les conflits

Reprenons l’exemple des images alignées verticalement dans la StackView. De base, je leur ai fixé des contraintes plus ou moins égales à une valeur, sur la hauteur. Par exemple, j’ai décidé que mon image de la Reine des Neige ferait plus de 100 de hauteur, alors que l’image de Minion ferait au moins 80 de hauteur. L’interface sera bien adaptée selon l’écran, mais l’image de la Reine des Neiges sera toujours forcément plus grande.

Seulement, dans le cas de la StackView, je vais avoir quelques petits ennuis. Comme je l’ai montré un peu plus haut, si je suis en mode Fill Equaly ou Fill Proportionally, je ne vais pas avoir de problème, mais ça ne va pas être aussi facile dans le cas de Fill ou Equal Spacing. Interface Builder m’avertit qu’il y a une ambiguïté sur la priorité des contraintes de mes images.

En effet, de base les contraintes sont aussi prioritaires les unes que les autres. Donc dans un cas comme celui-là, avec quasiment tout le contenu dynamique en termes de taille, il faut aiguiller le Layout Engine pour qu’à la compilation il comprenne comment placer les éléments.

Capture d’écran 2015-08-17 à 15.33.12
Du coup, comment donner plus de priorité à une contrainte plutôt qu’à une autre ? Dans les propriétés d’un élément, ici l’image de la Reine des Neiges, sous les contraintes listées se trouvent le « Content Hugging Prioriy » et le « Content Compression Resistance Priority ». Le « Content Hugging Priority » va définir si on souhaite que le contenu ait la possibilité de s’agrandir, en hauteur et largeur ; la valeur par défaut est de 250. Si on souhaite que le contenu ne puisse pas s’agrandir, il suffit de monter cette valeur à 251 (pas besoin de partir dans des valeurs trop grandes). En revanche, si on veut que le contenu puisse s’agrandir, on va lui donner la valeur de 249 (par exemple).

Au contraire le « Content Compression Resistance Priority » va définir si on souhaite ou non que le contenu soit rétréci en cas de besoin. Même principe que pour le « Content Hugging Priority », mis à part que la valeur par défaut est de 750, et que le fonctionnement est inversé : si on ne veut pas que le contenu soit rétréci, on va donner une valeur de 751 (749 sinon).

Ici, Interface Builder me propose comme solution de descendre la résistance de compression de mes trois premières images, et de monter celle de la dernière. Cela voudrait dire que j’autorise mes trois premières images à être rétrécies en cas de besoin, et donc avoir ma quatrième image qui ne pourra pas être rétrécie. Pourquoi pas, mais comme j’aime beaucoup la Reine des Neiges, je vais plutôt donner à ma quatrième image (le logo Xamarin) une priorité plus basse et une priorité plus haute à la Reine des Neiges. J’ai donc grâce à cela réglé les erreurs de contraintes, et ma StackView est configurée comme je le souhaite.

 

Petites astuces pour développeurs / intégrateurs aguerris

La première astuce qui peut être utile à tout moment lors de l’élaboration d’une interface, est de savoir comment nous pouvons ajouter des contraintes. La base. Tout d’abord, il est possible de le faire en cliquant sur un élément, en allant voir en bas à droite d’Interface Builder, on trouve trois boutons (ici il y en a quatre, mais le dernier n’est disponible qu’avec la bêta). En cliquant sur un de ces boutons, s’ouvre un menu de ce type (ci-dessous celui du troisième bouton) :

constraints-menu

Autre manière d’ajouter des contraintes : en faisant un ctrl + clic + drag depuis l’élément sur auquel je veux ajouter mes contraintes, vers son parent ou son voisin, ce qui va ouvrir un autre menu et définir les contraintes voulues.

Capture d’écran 2015-08-17 à 16.46.26

Ce menu apparaît si l’action est effectuée depuis le panneau d’arborescence des vues ou directement depuis les vues dans le storyboard.

Sans transition, une autre astuce, qui concerne les textes cette fois-ci ; il est conseillé d’utiliser aussi souvent que possible les propriétés Leading et Trailing concernant les bords gauche et droit des éléments. L’exemple très simple pour comprendre pourquoi faire comme cela, est lorsqu’on a un texte qui a une localisation : dans une certaine langue on va lire de gauche à droite, dans une autre de droite à gauche. Il faut donc pour cela adapter l’interface pour ne pas avoir quelque chose d’incohérent, ce qui se fait automatiquement en utilisant Leading et Trailing au lieu de droite et gauche. Plutôt pratique !

 

Dernière astuce : j’ai plusieurs éléments alignés, et j’aimerais avoir le même espace entre ces éléments. Comment me débrouiller avec mes contraintes ? Il suffit de placer des vues vides entre chaque élément.  Malgré son aspect cocasse, cette méthode peut s’avérer intéressante tout de même, d’autant plus qu’elle est approuvée par les équipes d’Apple. Cela dit elle aura beaucoup moins d’intérêt avec la StackView.

Comment cela fonctionne-t-il ? C’est très simple : il faut placer entre chaque élément une UIView vide, qui sera collée aux éléments juxtaposés. Pour qu’elles soient égales, il faut leur ajouter avec une contrainte forçant chacune des vues vides à avoir la même taille. Cela va permettre d’avoir une distance égale entre chaque élément, même si la vue change de taille.

 

Comment debugger les contraintes en cas de panique

Lorsqu’on réalise une interface pour de multiples appareils et orientations, il n’est pas rare d’être confronté à des problèmes de contraintes, que ce soit des contraintes manquantes, des ambiguïtés, ou encore des conflits.

Le premier réflexe dans ce cas est de regarder le détail depuis Interface Builder. Un indicateur rouge indique en haut à droite de la structure du storyboard s’il y a un problème. Si c’est le cas, celui-ci ou ceux-ci sont explicités dans une vue accessible depuis cet indicateur, et selon le problème il peut même y avoir des solutions proposées automatiquement.

Une autre solution est de regarder les logs de l’application. De base, si le Layout Engine s’emmêle les pinceaux avec les contraintes, dans les logs cela donne ça :

Capture d’écran 2015-08-19 à 16.46.48

On voit bien qu’il nous parle d’un problème lié aux contraintes, mais impossible de savoir laquelle.

Déjà, il faut savoir comment lire ces logs : ça se passe de bas en haut. On regarde d’abord la contrainte qui ne fonctionne pas (donc en bas), puis on remonte dans les logs pour voir quelles sont les contraintes qui causent ce disfonctionnement.

Ensuite, pour y voir plus clair, Interface Builder offre la possibilité de nommer les contraintes. L’identifiant d’une contrainte est défini depuis le détail de la contrainte. Après avoir correctement nommé les contraintes, les logs sont tout de suite plus clairs :

Capture d’écran 2015-08-19 à 17.08.09Tout ce texte est déjà plus facile à déchiffrer, on identifie rapidement ce qui pose problème, et il ne reste plus qu’à modifier ce qu’il faut !

Capture d’écran 2015-08-19 à 17.37.49

 

 

La dernière solution est de s’aider du simulateur et du débuggeur. Celui-ci fournit une grande quantité d’informations sur l’application, que ce soit au niveau de la mémoire, du réseau, mais aussi de l’interface. Pour cela, depuis Xcode, on capture la hiérarchie de la vue (option disponible depuis le menu debugger), et on peut afficher les contraintes (voir capture d’écran ci-contre), la structure des vues (sous forme de plusieurs couches avec un effet 3D), etc.

Capture d’écran 2015-08-19 à 17.37.09

Attention cependant aux petits malins qui développent sur du Xamarin iOS, cette solution n’est pas encore possible.

Conclusion

Dans cet article, nous avons pu lister une partie de nouveautés et astuces qui ont été abordées sur le sujet du storyboard lors de la dernière WWDC, mais je vous invite à regarder le reste des vidéos disponibles, avec PDF et exemples.

Normalement, avec cet article et le précédent, un développeur iOS devrait avoir toutes les clés en main (ou presque) pour réussir à créer une interface pour iPhone et iPad, sans trop de difficultés. Alors, 1, 2, 3, codez !