AXOPEN

Swift/Xcode: Des vues XIB réutilisables dans votre storyboard

Introduction

L’approche Xcode et Storyboard

Xcode propose une interface intéressante afin de développer votre application au travers du ‘storyboard’. Sur celui-ci, l’ensemble des scènes peut être visualisé ainsi que les transitions entre celles-ci. Cet environnement est intéressant dans l’approche du ‘workflow’ (flux opérationnel) de votre application. Cependant, à défaut d’optimisation de cette interface, le storyboard se révèle très rapidement lourd (très lourd). A moins de posséder un Mac puissant, vous allez vite peiner à construire vos scènes sur cet interface.

xcode_storyboard_screenshot

 

 

Nouveauté IOS 9 : les storyboard reference

L’arrivée d’iOS 9 apporte un lot d’améliorations bienvenues dont les ‘storyboard reference’. Ces références vont permettre de découper en plusieurs parties le storyboard afin de l’alléger. 

Note : le gain en performance sur votre interface se répercute sur la lisibilité de votre application.

xcode_storyboard_reference_illustration

 

 

L’approche Xib

Une autre approche, lors de la construction de votre application, est celle des Xib (Xlm Interface Builder) ou Nib (Next Interface Builder). Le Xib, qui est la version avant précompilation, se présente comme un XML. Pour ceux qui utilisent Android Studio, cela peut vous rappeler les ‘layout’. Ici, chaque scène possède son fichier et les transitions sont toutes définies dans le code.

xcode_xib_illustration

 

Combiner Xib et Storyboard : l’approche hybride efficace

Savoir combiner ces 2 approches va permettre de construire des applications complexes, avec des scènes elles-même complexes, de façon plus rapide et plus structurée. Nous allons voir comment utiliser des Xib au sein du storyboard.

Cette approche hybride nous apportera des avantages conséquents :

  • Un storyboard présentant élégamment le ‘workflow’ de notre application sans être lourd à l’affichage/édition
  • La réduction conséquente du code du storyboard peut aussi rendre possible sa fusion dans l’optique d’un ‘versionnement’ avec Git de celui-ci (ce qui relevait de l’exploit il y a quelques temps)
  • Une véritable modularité des vues : réutilisables dans plusieurs scènes (toute modification de la vue est ainsi répercutée dans les différentes scènes).
  • La possibilité d’utiliser des Xib dans d’autres Xib (avec modération).

Avant d’aborder la mise en place, on se doit de préciser que les conteneurs peuvent aussi simuler ce genre de réutilisations de vues. Cependant, cette méthode présente ses inconvénients :

  • Le storyboard n’est pas allégé, au contraire, il gagne en complexité
  • L’application est plus lourde (du point de vue de l’exécution), plus d’objets sont instanciés pour créer la vue

Xib et Storyboard, comment ça marche ?

Nous allons utiliser la fonction loadNibNamed afin de créer la vue à partir du Xib. Cet appel se fera à l’initialisation de notre objet (dérivé de UIView).

Le problème réside dans le fait que la fonction loadNibNamed nous renvoie un nouvel UIView et nous ne pouvons pas transformer l’appelant en l’appelé. A l’image d’un ‘container’ classique du storyboard, nous aurons donc un UIView comme conteneur. La dernière subtilité résidera dans l’adaptation dynamique des tailles de nos 2 UIView.

Xib et Storyboard : Mise en place

Classe Subview

Pour simplifier l’intégration, nous allons créer une classe générique dont nos vues hériteront. Cela nous permettra de gagner du temps de développement.

import UIKit

class Subview: UIView
{
    private var contentView: UIView!

    // 1
    override init(frame: CGRect)
    {
        super.init(frame: frame)
        self.setup()
    }

    required init?(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)

        self.setup()
    }

    // 2
    func setup()
    {
        if let nibNameSafe = self.getNibName()
        {
            self.contentView = NSBundle.mainBundle().loadNibNamed(nibNameSafe, owner: self, options: nil).first as? UIView
            self.contentView.frame = self.bounds
            self.contentView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
            self.addSubview(contentView)
        }
    }

    // 3
    func getNibName() -> String?
    {
        return nil
    }
}
  • 1 – Nous modifions l’initialisation afin d’appeler automatiquement notre fonction de setup
  • 2 – Le setup est assez simple :
    • Nous devons récupérer le nom de la vue à instancier (voir le 3ème point)
    • Nous instancions cette vue, ajustons sa taille au conteneur et vérifions que les masques sont réglés pour être flexibles (ceux-ci créerons automatiquement les contraintes entre nos 2 vues)
    • Enfin, nous ajoutons la vue à notre conteneur
  • 3 – Cette fonction servira à être modifiée (polymorphe) afin de retourner le nom de la vue désirée. Si cela n’est pas fait, le premier test de notre fonction setup n’entraînera pas de bug mais la vue restera vide (vous pouvez provoquer une exception ou autre retour si vous le souhaitez).

Xib et classe de la vue

Nous allons créer un Xib d’une vue simple et sa classe associée. Pour l’exemple, nous ajouterons un IBOutlet afin de montrer comment lier un objet à notre classe.

import UIKit

class SubviewTest: Subview
{
    @IBOutlet var labelTest: UILabel!

    override func getNibName() -> String?
    {
        return "SubviewTest"
    }

    override func setup()
    {
        super.setup()

        self.labelTest.text = "Test"
    }
}

Comme vous pouvez le voir, nous allons lier un UILabel afin de faire apparaître le texte ‘Test’ dans la vue en guise d’exemple.

Notes :

  • nous pouvons utiliser le polymorphisme sur la fonction setup afin de faire les actions que nous voulons à l’initialisation de notre vue (sans jamais oublier d’appeler le setup de la classe Subview préalablement). 
  • la modification de la fonction getNibName donne le nom du fichier du xib que nous allons créer.

Créons maintenant le Xib :

xcode_subview_xib_illustration

Afin de lier les IBOutlet, vous devez utiliser le ‘File’s Owner’.

Utilisation dans le storyboard

Une fois votre vue créée, vous pourrez l’intégrer simplement dans votre storyboard en appliquant la classe correspondante à un UIView :

xcode_subview_storyboard_illustration

Note : l’UIView de votre storyboard représente le conteneur mais la vue s’y adaptera (de même pour les contraintes). Vous pouvez ainsi avoir les problèmes habituels de contraintes, cependant, vous ne bénéficierez pas toujours des alertes de Xcode car la vue n’est pas rendue dans le storyboard. Ici, la hauteur sera forcée par la Subview car la contrainte dans le storyboard a une priorité de 1 (une sorte de ‘placeholder’).

Le résultat de cet exemple :

xcode_subview_result_illustration

La vue possédait un fond bleuté et, son conteneur, un fond rouge. En effet, vous pouvez voir que le résultat montre un fond bleu car conteneur et contenant partagent les mêmes dimensions.

Conclusion

Le recours à ces vues réutilisables est parti de la conception d’une application très vaste dont beaucoup de scènes partageaient les même composants. L’utilisation des ‘Subview’ présentée ici a permis d’alléger considérablement le storyboard ainsi que de développer les nouvelles vues dans des écrans plus épurés. Sans cette méthode, l’ajout de vues aurait été de plus en plus difficile et long car l’interface serait devenue bien trop gourmande en CPU.

Enfin, pour un travail en groupe et/ou sur Git, ce découpage de vues s’avère très puissant et accélère considérablement le développement de grandes applications.