Salut la compagnie ! Ravi de vous retrouver pour avancer pas à pas vers la création d’une app ! La dernière fois on a vu à quoi ressemblait l’interface builder d’Xcode et cette fois-ci on va s’attaquer à un gros morceau : les contrôleurs de vue (ou View Controller). On avait déjà très rapidement parlé des contrôleurs de vue mais sans vraiment saisir leur utilité et surtout leur fonctionnement. Il est important que vous ayez déjà lu les tutos sur la POO pour bien comprendre ce que l’on va faire aujourd’hui. Alors, à vos clavier !
Présentation
Alors déjà, à quoi ça sert ? Prenons la scène d’un théâtre. Elle est le support indispensable à son contenu et permet d’y présenter des décors, des lumières, des acteurs, etc… Elle n’est cependant pas inerte et est capable de s’adapter à son environnement en se surélevant, en ouvrant ou fermant ses rideaux ou en dissimulant des entrées secrètes. Elle est donc un support modulable pour le bon déroulement d’une pièce de théâtre.
Eh bien un contrôleur de vue c’est exactement la même chose qu’une scène. Il permet la disposition des objets, l’interactivité, possède ses propres mécanismes très complexes et sophistiqués pour son bon fonctionnement, et tout ce qu’il nous reste à y faire, c’est d’y déposer des acteurs et d’inventer une histoire.
Nous allons donc créer une nouvelle application iOS (single view) dans l’IDE que nous préférons afin de comprendre un peu mieux de quoi il s’agit. Une fois créé, nous pouvons tout de suite ouvrir le fichier ViewController.swift du projet pour y trouver ce code :
1 2 3 4 5 6 7 8 9 10 11 12 | import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } } |
On va rapidement analyser ça, tout d’abord nous importons le module UIKit nécessaire à l’interface graphique et donc les contrôleurs de vue. Ensuite vient la déclaration de la classe ViewController héritant du type UIViewController (cf : l’héritage des classes). Dans cette classe se trouve pour l’instant une seule fonction réécrite depuis son type d’origine : viewDidLoad().
Les plus attentifs se demanderont pourquoi la seule fonction présente dans un contrôleur de vue lors de sa création (on imagine alors qu’il s’agit du point de départ de la classe) n’est pas tout simplement la fonction init() ? Eh bien pour répondre à cette question il est nécessaire de bien comprendre comment la classe est chargée. Nous avons vu dans le premier volet détaillant la structure d’une app iOS que les contrôleurs de vue étaient appelés par le storyboard. L’initialisation n’est donc pas (dans ce cas là) sous notre contrôle et nous ne savons pas quels sont les paramètres que donnent le storyboard à la classe et surtout à quel moment.
De plus nous travaillons sur un objet gérant un affichage graphique qui nécessite un grand nombre de modules et de calculs avant d’être utilisable, il existe donc un temps de chargement entre l’initialisation de la classe et le moment où nous pouvons l’utiliser librement, c’est pour cela que l’on entame la classe directement à la fonction viewDidLoad() (qui comme son nom l’indique est appelée lorsque la classe est chargée).
Chronologie du chargement d’un contrôleur
Puisque je sens votre petit regard confus en coin, je me permet de vous faire un schéma expliquant l’ordre de chargement d’un contrôleur de vue.
Selon la quantité d’objets à afficher sur l’écran, ces opérations peuvent prendre plusieurs secondes, il est donc très important d’organiser son code en fonction de cela.
Lier le code et l’interface
Dans le fichier main.storyboard de notre projet on y trouvera notre scène avec notre contrôleur de vue en plein milieu qui nous attends :
On sait qu’il s’agit de notre contrôleur de vue et pas d’un contrôleur de vue car le nom de notre classe y est attaché dans l’inspecteur d’identité dans la colonne de droite d’Xcode. Si l’envie nous prends (et elle nous prendra ! ) d’ajouter d’autre contrôleurs de vues à notre app on pourra les lier à notre code via ce champ de texte.
Bien, ajoutons un élément à notre vue (voir : Structure d’un app iOS : l’interface Builder), par exemple un simple « label ». Si on retourne dans notre fichier ViewController.swift, on s’apercevra qu’aucun code n’a été ajouté ou modifié alors pourtant que l’on a ajouté un élément à notre vue. Il va donc falloir faire le lien nous-même via l’onglet « Outlets » de notre inspecteur, toujours situé à droite de la fenêtre :
Il faut bien s’assurer d’avoir sélectionné notre label, on peut s’en assurer car il est surligné dans la vue hiérarchique de gauche.
Les outlets
Les outlets (littéralement out let : sortie de constante) nous permettent de lier par une magie Xcodienne notre storyboard à notre code, que ce soit ses éléments ou ses différentes interactions. On va donc créer un « Referencing outlet » servant à lier notre élément à une variable. Pour ce faire nous allons diviser notre espèce de travail en deux via un tout petit icône situé en haut à droite de la vue principale de l’éditeur d’interface :
Voilà donc notre fenêtre principale divisée en deux avec des deux cotés notre fichier Main.storyboard. Notez par ailleurs qu’on ne peut pas modifier le même fichier aux deux endroits en même temps, l’aperçu ne se fait que sur la fenêtre active mais passons. Nous allons devoir afficher notre fichier ViewController sur l’écran de droite pour qu’il soit ouvert en même temps que notre storyboard. Dans la fenêtre de droite en haut à gauche on trouvera un bouton similaire qui nous permettra de faire ça simplement : . Lorsqu’on clique dessus s’ouvre un menu déroulant nous proposant divers choix, nous allons nous contenter d’aller dans la rubrique « Automatic » pour y trouver notre fichier swift qui s’y trouvé déjà.
Une fois notre fichier chargé, nous pouvons re-sélectionner notre label dans la vue de gauche puis de nouveau dans l’inspecteur d’outlets sur la droite, cliquer sur le petit rond à coté du bouton « New referencing outlet » et le faire glisser jusque dans notre code :
Une fois la bouton de la souris relâché, une petite fenêtre nous demande quelques informations sur la variable que l’on s’apprête à créer. Nous allons nous contenter pour l’instant de lui donner un nom et d’appuyer sur « Connect« .
Et hop ! Notre variable est incrustée dans notre fichier automatiquement (presque) :
1 2 3 4 5 6 7 8 9 10 11 12 13 | import UIKit class ViewController: UIViewController { @IBOutlet weak var label: UILabel! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } } |
Il est désormais facile de manipuler notre label directement depuis notre fichier swift, on peut par exemple modifier son contenu via sa propriété .text :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import UIKit class ViewController: UIViewController { @IBOutlet weak var label: UILabel! override func viewDidLoad() { super.viewDidLoad() label.text = "Swiftement !" } } |
On a plus qu’a lancer une compilation pour voir un peu le résultat :
Que se passe-t-il, mon texte est censuré !
Repositionnons tout ça
Ceux qui ont essayé avec du texte plus long que « label » auront le même problème. Rappelons nous que nous ne pouvons nous servir de la classe qu’une fois qu’elle a chargé. Et une fois qu’elle est chargée les position et dimension de notre label sont déjà définies et Swift se moque pas mal que ça dépasse, il va donc se contenter de rajouter 3 petits points qui est la réaction standard d’un UILabel sur iOS. Pour palier à ce problème, il suffit d’agrandir la taille du champ de texte directement dans l’interface builder ou bien dans le code en modifiant la valeur « frame » de notre variable label.
La frame est de type CGRect (core graphics rectangle) qui comme son nom l’indique permet de définir un rectangle via un point d’origine et deux dimensions. Le point d’origine (x, y) est calculée par rapport à l’angle supérieur gauche de la vue dans laquelle il est situé. X représente l’axe horizontal (les abscisses) et Y l’axe horizontal (les ordonnées. Via une combinaison x, y on peut ainsi se situer n’importe où dans la vue (y compris dans les valeurs négatives, en dehors de l’écran).
Nous allons donc créer une nouvelle variable contenant notre point d’origine qui sera de type CGPoint (core graphics point) et qui se déclare très simplement avec ses variables x et y :
1 | let position: CGPoint = CGPoint(x: 0, y: 0) |
Notons que les valeurs sont à écrire en pixels sans tenir compte de la résolution de l’écran mais ça, je le réserve pour une autre fois, restons concentrés.
Il nous suffit maintenant de créer une variable contenant les dimensions, cette fois de type CGSize (vous avez deviné ?) comprenant la largeur (width) et la hauteur (height) :
1 | let size: CGSize = CGSize(width: 200, height: 20) |
Et nous pouvons les combiner en une troisième variable de type CGRect que nous pourrons définir comme frame de notre label :
1 2 | let rect: CGRect = CGRect(origin: position, size: size) label.frame = rect |
Relançons une build pour voir un peu ce que ca donne :
Notre texte est bien là, tout en haut à gauche (à cause du 0, 0) mais il possède maintenant la bonne largeur. On pourra ainsi s’amuser à placer le texte où on veut, on peut bien sur réutiliser la position qu’on lui avait défini à l’origine, ou se servir de la taille de la vue dans laquelle nous travaillons via self.view.frame.size
. Je vous laisse réfléchir à tout ça et essayer un maximum pour se familiariser avec l’outil. Avant de vous quitter je vous donne le code complet de positionnement de notre élément via le code, et vu la complexité du sujet je pense faire une deuxième partie afin d’approfondir. A bientôt les amis 😉
1 2 3 4 5 6 7 8 9 10 11 | override func viewDidLoad() { super.viewDidLoad() let position: CGPoint = label.frame.origin let size: CGSize = CGSize(width: 200, height: 20) let rect: CGRect = CGRect(origin: position, size: size) label.frame = rect label.text = "Swiftement !" } |