3

Structure d’une app iOS : les UIView

Cet article n'a pas été mis à jour depuis plus d'un an, il est possible que certaines informations ne soient plus à jour. Si vous rencontrez des erreurs ou des différences en le suivant, n'hésitez pas à commenter pour me le signaler.

Salut salut les addict au café swift ! On se retrouve pour continuer notre fascinant voyage au coeur d’une app iOS et après les contrôleurs de vues on va voir ce qu’ils contrôlent : les vues ! Eh oui c’est même la base de tout l’affichage de l’interface d’iOS, alors ca risque d’être pas mal utile. Super Génial In-crédible, j’ai hâte de voir ça !

Quelle belle vue !

Tu vois de quoi je parle ?

Une vue c’est un type de classe qui peut être affiché à l’écran en étant placée dans un contrôleur de vue. C’est un type intégré à UIKit et qui s’appelle par conséquent UIView. Tous les éléments de la librairie d’éléments de l’interface builder sont des sous-types qui héritent notamment de sa capacité à s’afficher à l’écran. Prenons un label que nous avions vu au dernier épisode, il s’agit d’un UILabel. Si on regarde dans la documentation Apple on voit qu’il s’agir d’une classe de type UIView.

Pour initialiser une nouvelle vue en Swift il nous suffit d’invoquer la classe:

1
let view: UIView = UIView()

A ce moment là il s’agit d’une vue vide, sans dimensions ni position qui ne peut pas vraiment être affichée, nous allons donc devoir lui donner quelques informations pour commencer. Nous avons vu la dernière fois que l’on pouvait modifier la taille d’un UILabel via son attribut frame, eh bien il a hérité de cet attribut de sa super-classe : UIView, il nous est donc possible de faire la même chose.

1
2
3
4
5
6
7
let position: CGPoint = CGPoint(x: 0, y: 0)
let size: CGSize = CGSize(width: 200, height: 20)
       
let rect: CGRect = CGRect(origin: position, size: size)

let view: UIView = UIView()
view.frame = size

On aurai très bien pu passer ce paramètre directement lors de l’initialisation de la classe comme ceci :

1
2
3
4
5
let position: CGPoint = CGPoint(x: 0, y: 0)
let size: CGSize = CGSize(width: 200, height: 20)
       
let rect: CGRect = CGRect(origin: position, size: size)
let view: UIView = UIView(frame: rect)

Afficher une vue

Il est très simple d’afficher une vue, il suffit de l’imbriquer dans une autre via la méthode .addSubview, de cette façon :

1
2
3
4
let view: UIView = UIView()
let subview: UIView = UIView()

view.addSubview(subview)

Et voilà, c’est tout, c’est facile, ça marche. Les contrôleurs de vue contrôlent une vue particulière sur laquelle nous allons greffer les autres dont nous avons besoin. La vue principale du contrôleur de vue est stockée dans une variable de ce contrôleur sour le nom view, pour y ajouter une vue quelconque il suffit d’appeler la méthode .addsubview de cette vue là :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import UIKit

class ViewController: UIViewController {

    // Une constante est déclarée dans la classe UIViewController tel que :
    // let view: UIView

    override func viewDidLoad() {
        super.viewDidLoad()

        let subview: UIView = UIView()

        view.addSubview(subview)
    }


}

La chose qui est bien c’est que l’on peut simplement stocker la vue dans une variable de la classe pour y accéder et la modifier plus tard.

Les attributs

Pour ceux qui n’ont pas révisé leu POO les attributs sont les variables accessibles au sein d’une fonction. Nous en connaissons déjà un qui est .frame permettant de définir la position et la taille de la vue mais il en existe d’autres très pratiques, nous allons en voir quelques uns. Notons que le simple fait de modifier la valeur a pour effet de mettre tout de suite l’affichage a jour.

  • .center : CGPoint : très pratique car nous donne accès en permanence au point central du rectangle de notre view par rapport à sa super-view. Modifiable à tout moment de la même façon que la frame
  • .alpha : CGFloat : de 0 à 1 définit l’opacité générale de la vue et tous ses sous éléments, 1 étant 100% d’opacité
  • .backgroundColor : UIColor : couleur de fond de la vue, blanc par défaut
  • .transfrom : CGAffineTransform : toute transformation affine pouvant être appliquée à la vue
  • .isHidden : Bool : détermine si la vue doit être cachée ou affichée
  • .subviews: [UIView] : tableau des sous-vues ajoutées à celle-ci
  • .superview : UIView : vue dans laquelle celle-ci est ajoutée

Tous les types de vues partageront ces variables, par exemple les UILabel dont nous avons parlé la dernière fois ou bien les vues d’image, les boutons… Pour les voir un petit peu en action, nous pouvons créer un nouveau playground iOS de type single view dans Xcode. Nous obtenons par défaut un petit code nous permettant d’afficher un contrôleur de vue dans la vue interactive de l’éditeur de playground :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        let label = UILabel()
        label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
        label.text = "Hello World!"
        label.textColor = .black
       
        view.addSubview(label)
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

On voit ici dans ce code les attributs .frame et .backgroundColor utilisés sur la vue principale et le champ de texte qui sont communs à ces deux types de vues, et les attributs .text et .textColor réservés au type de vue UILabel.

Note : par défaut dans un playground c’est la fonction loadView() qui va être le point d’entrée du contrôleur de vue et non viewDidLoad() comme nous l’avons vu dans les projets. Cela implique que la vue intégrée au contrôleur n’est pas encore créée dans cet exemple et doit être initialisée pour éviter une boucle infinie (si la fonction loadView() ne charge pas… de vue !).

Les méthodes

Chaque vue est également équipé de sa propre boite à outils permettant d’interagir avec. Pour rappel, les méthodes sont les fonctions contenues dans les classes, et donc ici dans la classe UIView. En voici quelques-unes particulièrement utiles :

  • .addSubview(_ view: UIView) : ajoute une sous-vue
  • .removeFromSuperview() : supprime la vue de sa super-view. Simple et efficace
  • .bringSubviewToFront(_ view: UIView) : permet de faire passer une sous-vue au premier plan par rapport aux autres sous-vues
  • .sendSubviewToBack(_ view: UIView) : fait passer une sous-vue à l’arrière plan par rapport aux autres sous-vues
  • .insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView) : permet d’insérer une sous-vue par dessus une autre
  • .insertSubview(_ view: UIView, belowSubview siblingSubview: UIView) : permet d’insérer une sous-vue en dessous d’une autre
  • .isDescendant(of view: UIView) -> Bool : permet de savoir si une vue est contenue dans une autre en retournant un booléen

Ces différents méthodes vont pouvoir être exécutée sur n’importe quelle UIView de notre code. Voici un rapide exemple d’utilisation mais je ne vais pas plus m’attarder dessus que ça :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// On initialise nos vues afin de jouer avec
let container = UIView()
let sub1 = UIView()
let sub2 = UIView()

// On ajoute sub1 comme sous-vue de container
container.addSubview(sub1)

// On ajoute la vue sub2 en dessous de la vue sub1 à l'intérieur de container
// Le code fonctionne de la même façon avec aboveSubview mais dans cet exemple revient à un simple addView
container.insertSubview(sub2, belowSubview: sub1)

// On revoit sub1 à l'arrière plan de sub2 dans container
// Le fonctionnement est le même pour bringSubviewToFront
container.sendSubviewToBack(sub1)

// Si sub1 est contenue dans container
if sub1.isDescendant(of: container) {
    // On supprime la vue sub1
    sub1.removeFromSuperview()
}

Il existe aussi des fonctions que l’OS va appeler pour nous quand certains évènements vont se produire dans la classe UIView, comme un toucher, l’ajout d’une sous-vue, etc… Pour pouvoir accéder à ces fonctions nous allons réécrire une classe et lui donner le type UIView, comme on fait avec les contrôleurs de vue :

1
2
3
class MaVue: UIView {

}

Et à partir de là il nous suffit d’overrider les fonctions qui nous intéressent afin de profiter un maximum des fonctionnalités proposées UIKit, en voici quelques unes vraiment pratiques :

  • func willMove(toSuperview newSuperview: UIView?) : appelée avant que la vue soit chargée dans sa super-vue, souvent utilisée comme point d’entrée de la classe
  • func didMoveToSuperview() : appelée une fois que la vue a été chargée dans sa super-vue
  • func didAddSubview(_ subview: UIView) : cette méthode est appelée chaque fois qu’une sous-vue est ajoutée, nous y donnant un accès direct dans la fonction en la passant en attribut.
  • func willRemoveSubview(_ subview: UIView) : sera appelée chaque fois que l’on supprime une sous-vue. Une fois la fonction exécutée, la sous-vue sera supprimé (c’est donc notre dernière chance d’interagir avec).
  • func willRemoveSubview(_ subview: UIView) : sera appelée chaque fois que l’on supprime une sous-vue. Une fois la fonction exécutée, la sous-vue sera supprimé (c’est donc notre dernière chance d’interagir avec).

On s’en servira très classiquement comme avec n’importe quelle méthode à overrider dans une classe :

1
2
3
4
5
6
class MaVue: UIView {
    override func willMove(toSuperview newSuperview: UIView?) {
        // On peut accéder à la super-vue qui est passée par la variable : newSuperview
    }
   
}

Et si on utilisait le tactile

Vous vous en êtes surement rendu compte, les appareils iOS sur lesquels nous nous exerçons possèdent tous un écran tactile qui est une partie plutôt importante (la seule ?) de l’interface. C’est dans les UIView que l’on va pouvoir faire mumuse avec nos doigts grâce à quatre méthodes qui vont nous informer de tout ce qui touche l’écran :

  • func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) : un doigt vient de toucher l’écran
  • func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) : un doigt vient de bouger
  • func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) : un doigt s’est enlevé de l’écran
  • func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) : le toucher a été interrompu (par exemple par une alerte système)

Chacune de ses fonctions nous passent deux variables : touches et event. Touches est un Set dont nous n’avons pas encore parlé mais qui peut s’assimiler à un tableau classique, par conséquent correspond à une liste des touchers sur l’écran. L’event est une structure du système permettant de manipuler les touchers avec d’autres fonctions dont nous n’aurons pas besoin avant un moment, on va simplement apprendre à récupérer la position de chaque toucher dans la vue.

Pour ce faire, on va récupérer le premier toucher dans la liste (le seul si il n’y a qu’un doit de posé) :

1
2
3
4
5
6
7
class MaVue: UIView {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first // le premier toucher de la liste
        let pos = touch?.location(in: self) // la position CGPoint du premier toucher dans la vue elle-même
    }
   
}

 

Facile non ? Même procédé pour les autres fonctions relatives au toucher. Vous aurez surement remarqué le point d’interrogation après touch qui indique que la variable est optionnelle car récupérée depuis une liste. Pas d’inquiétude, les variables optionnelles sont le sujet d’un prochain tutoriel, mais je pense que pour aujourd’hui on va s’arrêter ici, je suis sûr que vous avez des centaines de tests à faire afin de bien comprendre comment les vues interagissent entre elles. Je vous laisse faire ça et vous retrouve une prochaine fois pour de nouvelles aventures !

Suite du tutoriel Structure d'une app iOS

En cours de rédaction...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *