Avez-vous un gros UIViewController? Pour beaucoup, oui. D'une part, cela fonctionne avec les données, d'autre part - avec l'interface.
Les tâches de séparation de la logique de l'interface sont décrites dans des centaines d'articles sur l'architecture: MVP, MVVM, VIPER. Ils résolvent le problème du flux de données, mais ne répondent pas à la question de savoir comment travailler avec l'interface: à un endroit il reste la création d'éléments, la mise en page, la configuration, le traitement des entrées et l'animation.
Séparons la vue du contrôleur et voyons comment loadView () nous aide.

L'interface d'application pour iOS est la hiérarchie
UIView
. Les tâches de chaque
view
: créer des éléments, personnaliser, organiser par endroits, animer. Cela peut être vu à partir des méthodes qui sont dans la classe
UIView: addSubview(), drawRect(), layoutSubviews().
Si vous regardez les méthodes de la classe
UIViewController
, vous pouvez voir qu'elle gère la
view:
charge, répond aux écrans de chargement et aux actions de l'utilisateur et affiche de nouveaux écrans. Souvent, le code qui devrait être dans
UIView
, nous écrivons dans les sous-classes de
UIViewController
, cela le rend trop volumineux. Séparez-le.
loadView ()
Le cycle de vie d'un
UIViewController
commence par
loadView()
. Une implémentation simplifiée ressemble à ceci:
Nous pouvons remplacer la méthode et spécifier notre classe.
super.loadView()
n'a pas besoin d'être appelé!
Implémentation de CustomView.swift Le contrôleur charge
CustomView,
ajoute à la hiérarchie, expose le
.frame
. La propriété
.view
sera la classe dont nous avons besoin:
Mais alors que le compilateur ne connaît pas la classe et pense qu'il existe une
UIView
normale. Corrigeons cela avec une fonction de conversion de type:
Vous pouvez maintenant voir les variables
CustomView
:
Simplifiez avec le type associéRuslan Kavetsky a proposé de supprimer la duplication de code en utilisant l'extension de protocole:
protocol ViewSpecificController { associatedtype RootView: UIView } extension ViewSpecificController where Self: UIViewController { func view() -> RootView { return self.view as! RootView } }
Pour chaque nouveau contrôleur, il vous suffit de spécifier le protocole et la sous-classe pour son UIView
via des typealias
:
Code dans une sous-classe de UIView
Création et configuration de contrôles
Les polices, les couleurs, les constantes et la hiérarchie peuvent être définies directement dans le constructeur CustomView:
layoutSubviews ()
Le meilleur endroit pour une mise en page manuelle est la méthode
layoutSubviews()
. Il est appelé à chaque fois que la taille de la
view
est modifiée, vous pouvez donc vous fier à la taille des
bounds
pour les calculs corrects:
Contrôles privés, propriétés publiques
S'il reste du temps, je rend les contrôles de
property
privés, mais je les gère via des variables ou fonctions publiques «dans le domaine de la connaissance». Un exemple plus simple:
L'avantage de l'encapsulation: la logique interne est cachée derrière l'interface. Par exemple, la validité d'un objet peut être indiquée par la couleur de la zone, pas par le carré, mais le contrôleur n'en saura rien.
Que reste-t-il dans viewDidLoad ()?
Si vous utilisez Interface Builder, alors
viewDidLoad()
souvent vide. Si vous créez une
view
dans le code, vous devez alors lier leurs actions via le modèle cible-action, ajouter un
UIGestureRecognizer
ou lier des délégués.
Personnalisable via Interface Builder
La sous-classe à
view
peut être configurée via Interface Builder (ci-après IB).
Vous devez sélectionner l'objet de
view
(pas le contrôleur) et définir sa classe. Il n'est pas nécessaire d'écrire votre propre
loadView()
, le contrôleur le fera lui-même. Mais
UIView
toujours
UIView
type
UIView
.

IBOutlet dans UIView
Si vous sélectionnez le contrôle dans la
view
, l'Assistant Éditeur reconnaît la classe
UIView
et la propose comme deuxième fichier en mode automatique. Vous pouvez donc transférer
IBOutlet
pour
view
.

Si ne fonctionne pasOuvrez la classe
CustomView
manuellement, écrivez
IBOutlet
. Vous pouvez maintenant faire glisser le marqueur et survoler un élément dans IB.

Si vous créez une interface dans le code, tous les objets sont accessibles après
init()
, mais lorsque vous travaillez avec IB, l'accès à
IBOutlet
apparaît uniquement après le chargement de l'interface depuis
UIStoryboard
dans la méthode
awakeFromNib()
:
IBAction dans UIViewController
À mon goût, le contrôleur devrait laisser toutes les actions de l'utilisateur. De standard:
- action-cible à partir des contrôles
- déléguer l'implémentation dans
UIViewController
- mise en œuvre de bloc
- réaction à la
Notification
Dans ce cas, l'
UIViewController
contrôle uniquement l'interface. Tout ce qui concerne la logique métier doit être retiré du contrôleur, mais c'est un choix: MVP, VIPER, etc.
Objectif-c
Dans Objective-C, vous pouvez remplacer complètement le type
UIView
. Pour ce faire, déclarez la propriété avec la classe souhaitée, remplacez
setter
et
getter
, en spécifiant la classe:
La fin
Dans l'exemple sur GitHub, vous pouvez regarder la séparation des classes pour une tâche simple: la couleur du carré dépend de sa position (dans la zone verte c'est vert, à l'extérieur c'est rouge).
Plus l'écran est complexe, meilleur est l'effet: le contrôleur est réduit, le code est transféré à sa place. Le code est simplement porté pour être
view
, mais l'encapsulation facilite l'interaction et la lecture du code. Parfois, la
view
peut être réutilisée avec un autre contrôleur. Par exemple, différents contrôleurs pour iPhone et iPad réagissent à leur manière à l'apparence du clavier, mais cela ne change pas le code d'
view
.
J'ai utilisé ce code dans différents projets et avec différentes personnes, chaque fois que l'équipe saluait la simplification et reprenait la pratique. J'espère que ça vous plaira aussi. Tout facile
UIViewController
!