La conception atomique et la conception de systèmes sont populaires dans la conception: c'est à ce moment-là que tout est composé de composants, des commandes aux écrans. Il n'est pas difficile pour un programmeur d'écrire des contrôles séparés, mais que faire avec des écrans entiers?
Jetons un œil à l'exemple du Nouvel An:
- collons tout ensemble;
- divisé en contrôleurs: sélectionnez la navigation, le modèle et le contenu;
- réutiliser le code pour d'autres écrans.

Tout en un tas
L'écran de cette nouvelle année parle des heures d'ouverture spéciales des pizzerias. C'est assez simple, donc ce ne sera pas un crime d'en faire un contrôleur:

Mais. La prochaine fois, lorsque nous aurons besoin d'un écran similaire, nous devrons le répéter à nouveau, puis apporter les mêmes modifications à tous les écrans. Eh bien, cela ne se produit pas sans modifications.
Par conséquent, il est plus raisonnable de le diviser en parties et de l'utiliser pour d'autres écrans. J'en ai souligné trois:
- navigation
- un modèle avec une zone de contenu et une place pour les actions en bas de l'écran,
- contenu unique au centre.
Sélectionnez chaque pièce dans son propre UIViewController
.
Navigation en conteneurs
Les exemples les plus frappants de conteneurs de navigation sont UINavigationController
et UITabBarController
. Chacun occupe une bande sur l'écran sous ses propres contrôles et laisse l'espace restant pour un autre UIViewController
.
Dans notre cas, il y aura un conteneur pour tous les écrans modaux avec un seul bouton de fermeture.
À quoi ça sert?Si nous voulons déplacer le bouton vers la droite, nous n'aurons besoin de le modifier que dans un seul contrôleur.
Ou, si nous décidons d'afficher toutes les fenêtres modales avec une animation spéciale, et de fermer de manière interactive avec un glissement, comme dans les cartes d'histoire de l'AppStore. UIViewControllerTransitioningDelegate
devra alors être défini uniquement pour ce contrôleur.

Vous pouvez utiliser une container view
pour séparer les contrôleurs: elle créera une UIView
dans le parent et y insérera l' UIView
contrôleur enfant.

Étirez la container view
jusqu'au bord de l'écran. Safe area
s'appliquera automatiquement au contrôleur enfant:

Modèle d'écran
Le contenu est évident à l'écran: image, titre, texte. Le bouton semble en faire partie, mais le contenu est dynamique sur différents iPhones et le bouton est fixe. Deux systèmes avec des tâches différentes sont visibles: l'un affiche le contenu et l'autre l'incorpore et l'aligne. Ils doivent être divisés en deux contrôleurs.

Le premier est responsable de la disposition de l'écran: le contenu doit être centré et le bouton cloué au bas de l'écran. Le second dessinera le contenu.

Sans modèle, tous les contrôleurs sont similaires, mais les éléments dansent.
Les boutons du dernier écran sont différents - cela dépend du contenu. La délégation aidera à résoudre le problème: le contrôleur-modèle demandera des contrôles du contenu et les affichera dans son UIStackView
.
Les boutons peuvent être attachés au contrôleur via des objets associés. Leur IBOutlet
et IBAction
sont stockés dans le contrôleur de contenu, seuls les éléments ne sont pas ajoutés à la hiérarchie.

Vous pouvez obtenir des éléments du contenu et les ajouter au modèle lors de la préparation de UIStoryboardSegue
:
Dans le setter, nous ajoutons des contrôles à UIStackView
:
En conséquence, notre contrôleur a été divisé en trois parties: navigation, modèle et contenu. Dans l'image, toute la container view
affichée en gris:

Taille du contrôleur dynamique
Le contrôleur de contenu a sa propre taille maximale, il est limité par des constraints
internes.
Container view
ajoute des constores basés sur le Autoresizing mask
et ils entrent en conflit avec les dimensions internes du contenu. Le problème est résolu dans le code: dans le contrôleur de contenu, vous devez indiquer qu'il n'est pas affecté par les constores du Autoresizing mask
:

Il existe deux autres étapes pour Interface Builder:
Étape 1. Spécifiez la Intrinsic size
de l' UIView
. Les vraies valeurs apparaîtront après le lancement, mais pour l'instant, nous en mettrons toutes appropriées.

Étape 2. Pour le contrôleur de contenu, spécifiez Simulated Size
. Il peut ne pas correspondre à la taille passée.
Il y a eu des erreurs de mise en page, que dois-je faire?Des erreurs se produisent lorsque AutoLayout
ne peut pas comprendre comment décomposer les éléments dans la taille actuelle.
Le plus souvent, le problème disparaît après avoir changé les priorités de la constante. Vous devez les déposer afin que l'un des UIView
puisse s'étendre / se contracter plus que les autres.
Nous divisons en parties et écrivons dans le code
Nous avons divisé le contrôleur en plusieurs parties, mais jusqu'à présent, nous ne pouvons pas les réutiliser, l'interface d' UIStoryboard
difficile à extraire en plusieurs parties. Si nous devons transférer certaines données vers le contenu, nous devrons alors les utiliser dans toute la hiérarchie. Cela devrait être l'inverse: prenez d'abord le contenu, configurez-le, puis enveloppez-le dans les conteneurs nécessaires. Comme une ampoule.
Trois tâches apparaissent sur notre chemin:
- Séparez chaque contrôleur dans son propre
UIStoryboard
. - Refuser la
container view
, ajouter des contrôleurs aux conteneurs dans le code. - Attachez tout en arrière.
Partager UIStoryboard
Vous devez créer deux UIStoryboard
supplémentaires et y copier-coller le contrôleur de navigation et le contrôleur de modèle. Embed segue
enchaînement Embed segue
sera interrompu, mais la container view
du container view
avec les contraintes configurées sera transférée. Les contraintes doivent être enregistrées et la container view
du container view
doit être remplacée par une UIView
.
Le moyen le plus simple consiste à modifier le type de vue Conteneur dans le code UIStoryboard.- ouvrir
UIStoryboard
tant que code (menu contextuel du fichier → Ouvrir en tant que ... → Code source); changez le type de containerView
en view
. Il est nécessaire de modifier les balises d'ouverture et de fermeture .
De la même manière, vous pouvez changer, par exemple, UIView
en UIScrollView
, si nécessaire. Et vice versa.

Nous définissons le contrôleur sur la propriété is initial view controller
, et UIStoryboard
appellerons UIStoryboard
tant que contrôleur.
Nous chargeons le contrôleur depuis UIStoryboard.Si le nom du contrôleur correspond au nom de UIStoryboard
, le téléchargement peut être encapsulé dans une méthode qui trouvera elle-même le fichier souhaité:
protocol Storyboardable { } extension Storyboardable where Self: UIViewController { static func instantiateInitialFromStoryboard() -> Self { let controller = storyboard().instantiateInitialViewController() return controller! as! Self } static func storyboard(fileName: String? = nil) -> UIStoryboard { let storyboard = UIStoryboard(name: fileName ?? storyboardIdentifier, bundle: nil) return storyboard } static var storyboardIdentifier: String { return String(describing: self) } static var storyboardName: String { return storyboardIdentifier } }
Si le contrôleur est décrit en .xib
, le constructeur standard se chargera sans ces danses. Hélas, .xib
ne peut contenir qu'un seul contrôleur, souvent cela ne suffit pas: dans un bon cas, un écran est composé de plusieurs. Par conséquent, nous utilisons UIStoryborad
, il est facile de diviser l'écran en plusieurs parties.
Ajouter un contrôleur dans le code
Pour que le contrôleur fonctionne correctement, nous avons besoin de toutes les méthodes de son cycle de vie: will/did-appear/disappear
.
Pour un affichage correct, vous devez appeler 5 étapes:
willMove(toParent parent: UIViewController?) addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
Apple suggère de réduire le code à 4 étapes, car addChild()
lui-même appellera willMove(toParent)
. En résumé:
addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
Pour plus de simplicité, vous pouvez envelopper le tout en extension
. Pour notre cas, nous avons besoin d'une version avec insertSubview()
.
extension UIViewController { func insertFullframeChildController(_ childController: UIViewController, toView: UIView? = nil, index: Int) { let containerView: UIView = toView ?? view addChild(childController) containerView.insertSubview(childController.view, at: index) containerView.pinToBounds(childController.view) childController.didMove(toParent: self) } }
Pour supprimer, vous avez besoin des mêmes étapes, mais au lieu du contrôleur parent, vous devez définir nil
. Maintenant, removeFromParent()
appelle didMove(toParent: nil)
, et la mise en page n'est pas nécessaire. La version raccourcie est très différente:
willMove(toParent: nil) view.removeFromSuperview() removeFromParent()
Disposition
Définir des contraintes
Pour définir correctement la taille du contrôleur, nous utiliserons AutoLayout
. Nous devons clouer tous les côtés de tous les côtés:
extension UIView { func pinToBounds(_ view: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: topAnchor), view.bottomAnchor.constraint(equalTo: bottomAnchor), view.leadingAnchor.constraint(equalTo: leadingAnchor), view.trailingAnchor.constraint(equalTo: trailingAnchor) ]) } }
Ajouter un contrôleur enfant dans le code
Maintenant, tout peut être combiné:
En raison de la fréquence d'utilisation, nous pouvons envelopper tout cela en extension
:
Une méthode similaire est également nécessaire pour le contrôleur de modèle. prepare(for segue:)
auparavant configurées dans prepare(for segue:)
, mais vous pouvez maintenant les lier dans la méthode d'intégration du contrôleur:
La création d'un contrôleur ressemble à ceci:
La connexion d'un nouvel écran au modèle est simple:
- supprimer ce qui n'est pas pertinent pour le contenu;
- spécifier des boutons d'action en implémentant le protocole OnboardingViewControllerDatasource;
- écrire une méthode qui relie un modèle et un contenu.
En savoir plus sur les conteneurs
Barre d'état
Il est souvent nécessaire que la visibilité de la status bar
soit contrôlée par un contrôleur avec du contenu, pas un conteneur. Il y a quelques property
pour cela:
En utilisant ces property
vous pouvez créer une chaîne de contrôleurs, ce dernier sera responsable de l'affichage de la status bar
.
Zone sûre
Si les boutons de conteneur chevauchent le contenu, vous devez augmenter la zone safeArea
. Cela peut être fait dans le code: définissez additinalSafeAreaInsets
pour les contrôleurs enfants. Vous pouvez l'appeler depuis embedController()
:
private func addSafeArea(to controller: UIViewController) { if #available(iOS 11.0, *) { let buttonHeight = CGFloat(30) let topInset = UIEdgeInsets(top: buttonHeight, left: 0, bottom: 0, right: 0) controller.additionalSafeAreaInsets = topInset } }
Si vous ajoutez 30 points au-dessus, le bouton cessera de chevaucher le contenu et safeArea
occupera la zone verte:

Marges. Préserver les marges de superview
Les contrôleurs ont des margins
standard. Habituellement, ils sont égaux à 16 points de chaque côté de l'écran et uniquement sur les tailles Plus, ils sont de 20 points.
En fonction des margins
vous pouvez créer des constantes, l'indentation sur le bord sera différente pour différents iPhones:

Lorsque nous mettons un UIView
dans un autre, les margins
sont divisées par deux: à 8 points. Pour éviter cela, vous devez inclure Preserve superview margins
. Les margins
UIView
enfant seront alors égales aux margins
parent. Il convient aux conteneurs plein écran.
La fin
Les contrôleurs de conteneurs sont un outil puissant. Ils simplifient le code, séparent les tâches et peuvent être réutilisés. Vous pouvez écrire des contrôleurs imbriqués de n'importe quelle manière: dans UIStoryboard
, en .xib
ou simplement en code. Plus important encore, ils sont faciles à créer et amusants à utiliser.
→ Un exemple d'un article sur GitHub
Avez-vous des écrans à partir desquels il serait utile de créer un modèle? Partagez dans les commentaires!