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

Xcode propose une interface intéressante afin de développer votre application au travers du ‘storyboard’.
Corentin RISSELINMis à jour le 13 Juin 2016
xcode_subview_head.jpg

Introduction

Lapproche Xcode et Storyboard

Xcode propose une interface intéressante afin de développer votre application au travers du storyboard. Sur celui-ci, lensemble des scènes peut être visualisé ainsi que les transitions entre celles-ci. Cet environnement est intéressant dans lapproche du workflow (flux opérationnel) de votre application. Cependant, à défaut doptimisation 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-1024x686.jpg

 

 

Nouveauté IOS 9 : les storyboard reference

Larrivée diOSSystème d'exploitation des appareils Apple. 9 apporte un lot daméliorations bienvenues dont les storyboard reference. Ces références vont permettre de découper en plusieurs parties le storyboard afin de lalléger._ _

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

xcode_storyboard_reference_illustration.jpg

 

 

Lapproche 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 AndroidAndroid est un système d'exploitation mobile basé sur Linux. 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-1024x828.jpg

 

Combiner Xib et Storyboard : lapproche 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 à laffichage/édition
  • La réduction conséquente du code du storyboard peut aussi rendre possible sa fusion dans loptique dun versionnement avec Git de celui-ci (ce qui relevait de lexploit 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é dutiliser des Xib dans dautres Xib (avec modération).

Avant daborder 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 nest pas allégé, au contraire, il gagne en complexité
  • Lapplication est plus lourde (du point de vue de lexécution), plus dobjets 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 à linitialisation 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 lappelant en lappelé. A limage dun container classique du storyboard, nous aurons donc un UIView comme conteneur. La dernière subtilité résidera dans ladaptation dynamique des tailles de nos 2 UIView.

Xib et Storyboard : Mise en place

Classe Subview

Pour simplifier linté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 linitialisation 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 nest 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 dune vue simple et sa classe associée. Pour lexemple, 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 dexemple.

Notes :

  • nous pouvons utiliser le polymorphisme sur la fonction setup afin de faire les actions que nous voulons à linitialisation de notre vue (sans jamais oublier dappeler 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-1024x374.jpg

Afin de lier les IBOutlet, vous devez utiliser le Files Owner.

Utilisation dans le storyboard

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

xcode_subview_storyboard_illustration-1024x640.jpg

Note : lUIView de votre storyboard représente le conteneur mais la vue sy 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 nest 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-164x300.jpg

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 dune application très vaste dont beaucoup de scènes partageaient les même composants. Lutilisation des Subview présentée ici a permis dalléger considérablement le storyboard ainsi que de développer les nouvelles vues dans des écrans plus épurés. Sans cette méthode, lajout de vues aurait été de plus en plus difficile et long car linterface serait devenue bien trop gourmande en CPU.

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