Bonjour, Habr! Je vous présente la traduction de l'article
Création d'éléments d'interface utilisateur à l'aide de PureLayout par Aly Yaka.

Bienvenue dans la deuxième partie de l'article sur la création par programmation d'une interface à l'aide de PureLayout.
Dans la première partie, nous avons créé l'interface utilisateur d'une simple application mobile entièrement en code, sans utiliser de Storyboards ou NIBs. Dans ce guide, nous couvrirons certains des éléments d'interface utilisateur les plus couramment utilisés dans toutes les applications:
- UINavigationController / Bar
- UITableView
- UITableViewCell à dimensionnement automatique
UINavigationController
Dans notre application, vous avez probablement besoin d'une barre de navigation pour que l'utilisateur puisse passer de la liste de contacts à des informations détaillées sur un contact spécifique, puis revenir à la liste.
UINavigationController
peut facilement résoudre ce problème en utilisant la barre de navigation.
UINavigationController
n'est qu'une pile dans laquelle vous déplacez de nombreuses vues. L'utilisateur voit la vue la plus élevée (celle qui a été déplacée en dernier) en ce moment (sauf lorsque vous avez une autre vue présentée en plus de cela, disons favoris). Et lorsque vous appuyez sur les contrôleurs de vue de dessus du contrôleur de navigation, le contrôleur de navigation crée automatiquement un bouton «retour» (en haut à gauche ou à droite selon les préférences linguistiques actuelles de l'appareil), et en appuyant sur ce bouton, vous revenez à la vue précédente.
Tout cela est pris en charge par le contrôleur de navigation. Et l'ajout d'un autre ne prendra qu'une seule ligne de code supplémentaire (si vous ne souhaitez pas personnaliser la barre de navigation).
Accédez à AppDelegate.swift et ajoutez la ligne de code suivante ci-dessous, laissez
viewController = ViewController ():
let navigationController = UINavigationController(rootViewController: viewController)
Maintenant, changez
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
. Dans la première ligne, nous avons créé une instance de
UINavigationController
et lui avons
viewController
notre
viewController
tant que
rootViewController
, qui est le contrôleur de vue tout en bas de la pile, ce qui signifie qu'il n'y aura jamais de bouton de retour sur la barre de navigation de cette vue. Ensuite, nous donnons à notre fenêtre un contrôleur de navigation en tant que
rootViewController
, car maintenant il contiendra toutes les vues de l'application.
Exécutez maintenant votre application. Le résultat devrait ressembler à ceci:
Malheureusement, quelque chose s'est mal passé. Il semble que la barre de navigation chevauche notre vue supérieure, et nous avons plusieurs façons de résoudre ce problème:
- Augmentez la taille de notre vue
upperView
pour l'adapter à la hauteur de la barre de navigation. - Définissez la propriété
isTranslucent
de la barre de navigation sur false
. Cela rendra la barre de navigation opaque (si vous ne l'avez pas remarqué, elle est un peu transparente), et maintenant le bord supérieur de la vue d'ensemble deviendra le bas de la barre de navigation.
Personnellement, je choisirai la deuxième option, mais vous étudierez la première. Je recommande également de vérifier et de lire attentivement les documents d'Apple sur
UINavigationController
et
UINavigationBar
:
Accédez maintenant à la méthode viewDidLoad et ajoutez cette ligne
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad ()
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad ()
, pour que cela ressemble à ceci:
override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.view.backgroundColor = .white self.addSubviews() self.setupConstraints() self.view.bringSubview(toFront: avatar) self.view.setNeedsUpdateConstraints() }
Vous pouvez également ajouter cette ligne
self.title = "John Doe" viewDidLoad
, qui ajoutera un "Profil" à la barre de navigation afin que l'utilisateur sache où il se trouve actuellement. Exécutez l'application et le résultat devrait ressembler à ceci:
Refonte de notre contrôleur de vue
Avant de continuer, nous devons réduire notre fichier
ViewController.swift
que nous ne puissions utiliser que de la vraie logique, pas seulement du code pour les éléments de l'interface utilisateur. Nous pouvons le faire en créant une sous-classe d'
UIView
et en y déplaçant tous nos éléments d'interface utilisateur. La raison pour laquelle nous faisons cela est de suivre le modèle architectural Model-View-Controller ou MVC pour faire court. En savoir plus sur MVC
Model-View-Controller (MVC) sur iOS: une approche moderne .
Maintenant,
ContactCard
clic droit sur le dossier
ContactCard
dans Project Navigator et sélectionnez «Nouveau fichier»:

Cliquez sur Cocoa Touch Class puis sur Suivant. Maintenant écrivez «ProfileView» comme nom de classe, et à côté de «Sous-classe de:» assurez-vous d'entrer «UIView». Il dit simplement à Xcode de faire automatiquement hériter notre classe de
UIView
, et il ajoutera du code passe-partout. Maintenant, cliquez sur Suivant, puis sur Créer et supprimer le code mis en commentaire:
Et maintenant, nous sommes prêts pour la refactorisation.
Coupez et collez toutes les variables paresseuses du contrôleur de vue dans notre nouvelle vue.
Sous la dernière variable en attente, remplacez
init(frame :)
en tapant
init
puis en sélectionnant le premier résultat de saisie semi-automatique à partir de Xcode.

Une erreur apparaîtra indiquant que l'initialiseur «requis» «init (coder :)» devrait être fourni par une sous-classe de «UIView»:

Vous pouvez résoudre ce problème en cliquant sur le cercle rouge, puis sur Corriger.

Dans tout initialiseur surchargé, vous devez presque toujours appeler l'initialiseur de superclasse, alors ajoutez cette ligne de code en haut de la méthode:
super.init (frame: frame)
.
Coupez et collez la méthode
addSubviews()
sous les initialiseurs et supprimez
self.view
avant chaque appel
addSubview
.
func addSubviews() { addSubview(avatar) addSubview(upperView) addSubview(segmentedControl) addSubview(editButton) }
Appelez ensuite cette méthode à partir de l'initialiseur:
override init(frame: CGRect) { super.init(frame: frame) addSubviews() bringSubview(toFront: avatar) }
Pour les restrictions, remplacez
updateConstraints()
et ajoutez un appel à la fin de cette fonction (où il restera toujours):
override func updateConstraints() {
Lors du remplacement d'une méthode, il est toujours utile de vérifier sa documentation en visitant les documents Apple ou, plus simplement, en maintenant la touche Option (ou Alt) enfoncée et en cliquant sur le nom de la fonction:

Coupez et collez le code de contrainte du contrôleur de vue dans notre nouvelle méthode:
override func updateConstraints() { avatar.autoAlignAxis(toSuperviewAxis: .vertical) avatar.autoPinEdge(toSuperviewEdge: .top, withInset: 64.0) upperView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom) segmentedControl.autoPinEdge(toSuperviewEdge: .left, withInset: 8.0) segmentedControl.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) segmentedControl.autoPinEdge(.top, to: .bottom, of: avatar, withOffset: 16.0) editButton.autoPinEdge(.top, to: .bottom, of: upperView, withOffset: 16.0) editButton.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) super.updateConstraints() }
Revenez maintenant au contrôleur de vue et initialisez l'instance
ProfileView
sur la méthode
viewDidLoad
let profileView = ProfileView(frame: .zero)
, ajoutez-la en tant que sous-vue au
ViewController
.
Maintenant, notre contrôleur de vue a été réduit à quelques lignes de code!
import UIKit import PureLayout class ViewController: UIViewController { let profileView = ProfileView(frame: .zero) override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.title = "Profile" self.view.backgroundColor = .white self.view.addSubview(self.profileView) self.profileView.autoPinEdgesToSuperviewEdges() self.view.layoutIfNeeded() } }
Pour vous assurer que tout fonctionne comme prévu, lancez votre application et vérifiez à quoi elle ressemble.
Avoir un contrôleur d'examen mince et soigné devrait toujours être votre objectif. Cela peut prendre beaucoup de temps, mais cela vous évitera des problèmes inutiles pendant la maintenance.
UITableView
Ensuite, nous ajouterons un UITableView pour présenter les informations de contact telles que le numéro de téléphone, l'adresse, etc.
Si vous ne l'avez pas déjà fait, rendez-vous dans la documentation Apple pour consulter UITableView, UITableViewDataSource et UITableViewDelegate.
Accédez à
ViewController.swift
et ajoutez
lazy var
pour
tableView
ci-dessus
viewDidLoad()
:
lazy var tableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self tableView.dataSource = self return tableView }()
Si vous essayez d'exécuter l'application, Xcode se plaindra que cette classe n'est ni un délégué ni une source de données pour
UITableViewController
, et donc nous ajouterons ces deux protocoles à la classe:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { . . .
Encore une fois, Xcode se plaindra d'une classe qui n'est pas conforme au protocole
UITableViewDataSource
, ce qui signifie qu'il existe des méthodes obligatoires dans ce protocole qui ne sont pas définies dans la classe. Pour savoir laquelle de ces méthodes vous devez implémenter tout en maintenant Cmd + Control, cliquez sur le protocole
UITableViewDataSource
dans la définition de classe et vous passerez à la définition de protocole. Pour toute méthode qui n'est pas précédée du mot
optional
, une classe correspondant à ce protocole doit être implémentée.
Ici, nous avons deux méthodes que nous devons implémenter:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
- cette méthode indique à la table vue le nombre de lignes que nous voulons afficher.public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
- cette méthode interroge la cellule de chaque ligne. Ici, nous initialisons (ou réutilisons) la cellule et insérons les informations que nous voulons montrer à l'utilisateur. Par exemple, la première cellule affichera le numéro de téléphone, la deuxième cellule affichera l'adresse, etc.
Revenez maintenant à
ViewController.swift
, commencez à taper
numberOfRowsInSection
et lorsque la saisie
numberOfRowsInSection
-automatique apparaît, sélectionnez la première option.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { <#code#> }
Supprimez le mot code et revenez maintenant 1.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 }
Sous cette fonction, commencez à taper
cellForRowAt
et sélectionnez à nouveau la première méthode de la saisie semi-automatique.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { <#code#> }
Et, encore une fois, pour l'instant, renvoyez un
UITableViewCell
.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() }
Maintenant, pour connecter notre vue de table à l'intérieur de
ProfileView
, nous allons définir un nouvel initialiseur qui prend la vue de table comme paramètre, afin qu'il puisse l'ajouter comme sous-vue et définir les restrictions correspondantes pour cela.
Accédez à
ProfileView.swift
et ajoutez l'attribut pour la vue de table juste au-dessus de l'initialiseur:
var tableView: UITableView!
déterminés, nous ne sommes donc pas sûrs que ce sera constant.
Remplacez maintenant l'ancienne implémentation
init (frame :)
par:
init(tableView: UITableView) { super.init(frame: .zero) self.tableView = tableView addSubviews() bringSubview(toFront: avatar) }
Xcode va maintenant se plaindre de l'
init (frame :)
manquante
init (frame :)
pour
ProfileView
, alors revenez à
ViewController.swift
et remplacez
let profileView = ProfileView (frame: .zero)
par
lazy var profileView: UIView = { return ProfileView(tableView: self.tableView) }()
Maintenant, notre
ProfileView
a un lien vers une vue de table, et nous pouvons l'ajouter en tant que sous-vue et définir les restrictions appropriées pour cela.
De retour à
ProfileView.swift
, ajoutez
addSubview(tableView)
à la fin de
addSubviews()
et définissez ces restrictions sur
updateConstraints()
sur
super.updateConstraints
:
tableView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top) tableView.autoPinEdge(.top, to: .bottom, of: segmentedControl, withOffset: 8)
La première ligne ajoute trois restrictions entre la vue de table et sa vue d'ensemble: les côtés droit, gauche et inférieur de la vue de table sont attachés aux côtés droit, gauche et inférieur de la vue de profil.
La deuxième ligne attache le haut de la vue de table au bas du contrôle segmenté avec un intervalle de huit points entre eux. Lancez l'application et le résultat devrait ressembler à ceci:
Génial, maintenant tout est en place et nous pouvons commencer à introduire nos cellules.
UITableViewCell
Pour implémenter
UITableViewCell
, nous aurons presque toujours besoin de sous-classer cette classe, donc
UITableViewCell
clic droit sur le dossier
ContactCard
dans le Navigateur du projet, puis "Nouveau fichier ...", puis "Cocoa Touch Class" et "Suivant".
Entrez «UITableViewCell» dans le champ «Sous-classe de:» et Xcode remplira automatiquement le nom de classe «TableViewCell». Saisissez «ProfileView» avant la saisie semi-automatique afin que le nom final soit «ProfileInfoTableViewCell», puis cliquez sur «Suivant» et «Créer». Allez-y et supprimez les méthodes créées, car nous n'en aurons pas besoin. Si vous le souhaitez, vous pouvez d'abord lire leurs descriptions pour comprendre pourquoi nous n'en avons pas besoin pour le moment.
Comme nous l'avons dit plus tôt, notre cellule contiendra des informations de base, qui sont le nom du champ et sa description, et nous avons donc besoin d'étiquettes pour elles.
lazy var titleLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Title" return label }() lazy var descriptionLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Description" label.textColor = .gray return label }()
Et maintenant, nous allons redéfinir l'initialiseur afin que la cellule puisse être configurée:
override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(titleLabel) contentView.addSubview(descriptionLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
En ce qui concerne les limitations, nous allons faire un peu différent, mais néanmoins très utile:
override func updateConstraints() { let titleInsets = UIEdgeInsetsMake(16, 16, 0, 8) titleLabel.autoPinEdgesToSuperviewEdges(with: titleInsets, excludingEdge: .bottom) let descInsets = UIEdgeInsetsMake(0, 16, 4, 8) descriptionLabel.autoPinEdgesToSuperviewEdges(with: descInsets, excludingEdge: .top) descriptionLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 16) super.updateConstraints() }
Ici, nous commençons à utiliser
UIEdgeInsets
pour définir l'espacement autour de chaque étiquette. Un objet
UIEdgeInsets
peut être créé à l'aide de la
UIEdgeInsetsMake(top:, left:, bottom:, right:)
. Par exemple, pour
titleLabel
nous disons que nous voulons que la limite supérieure soit de quatre points, et la droite et la gauche à huit. Nous ne nous soucions pas du bas, car nous l'excluons, car nous le fixons en haut de la marque de description. Prenez une minute pour lire et visualiser toutes les contraintes dans votre tête.
Ok, maintenant nous pouvons commencer à dessiner des cellules dans notre vue tabulaire. Passons à
ViewController.swift
et modifions l'initialisation paresseuse de notre vue de table pour enregistrer cette classe de cellules dans la vue de table et définir la hauteur de chaque cellule.
let profileInfoCellReuseIdentifier = "profileInfoCellReuseIdentifier" lazy var tableView: UITableView = { ... tableView.register(ProfileInfoTableViewCell.self, forCellReuseIdentifier: profileInfoCellReuseIdentifier) tableView.rowHeight = 68 return tableView }()
Nous ajoutons également une constante pour l'identifiant de réutilisation des cellules. Cet identifiant est utilisé pour supprimer les cellules de la vue tableau lorsqu'elles sont affichées. Il s'agit d'une optimisation qui peut (et devrait) être utilisée pour aider
UITableView
réutiliser les cellules qui étaient précédemment présentées pour afficher le nouveau contenu au lieu de redessiner la nouvelle cellule à partir de zéro.
Maintenant, laissez-moi vous montrer comment réutiliser des cellules dans une ligne de code dans la méthode
cellForRowAt
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: profileInfoCellReuseIdentifier, for: indexPath) as! ProfileInfoTableViewCell return cell }
Ici, nous informons la vue de table du retrait d'une cellule réutilisable de la file d'attente en utilisant l'identifiant sous lequel nous avons enregistré le chemin vers la cellule où l'utilisateur est sur le point d'apparaître. Ensuite, nous
ProfileInfoTableViewCell
la cellule à
ProfileInfoTableViewCell
pour pouvoir accéder à ses propriétés afin que nous puissions, par exemple, définir le titre et la description. Cela peut être fait en utilisant ce qui suit:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { ... switch indexPath.row { case 0: cell.titleLabel.text = "Phone Number" cell.descriptionLabel.text = "+234567890" case 1: cell.titleLabel.text = "Email" cell.descriptionLabel.text = "john@doe.co" case 2: cell.titleLabel.text = "LinkedIn" cell.descriptionLabel.text = "www.linkedin.com/john-doe" default: break } return cell }
Maintenant, définissez
numberOfRowsInSection
pour retourner «3» et lancez votre application.
Incroyable non?
Cellules à dimensionnement automatique
Peut-être, et très probablement, il y aura un cas où vous souhaitez que différentes cellules aient des hauteurs conformément aux informations qu'elles contiennent, ce qui n'est pas connu à l'avance. Pour ce faire, vous avez besoin d'une vue tabulaire avec des dimensions calculées automatiquement, et en fait, il existe un moyen très simple de le faire.
Tout d'abord, dans
ProfileInfoTableViewCell
ajoutez cette ligne à l'initialisation paresseuse
descriptionLabel
:
label.numberOfLines = 0
Revenez au
ViewController
et ajoutez ces deux lignes à l'initialiseur de vue de table:
lazy var tableView: UITableView = { ... tableView.estimatedRowHeight = 64 tableView.rowHeight = UITableViewAutomaticDimension return tableView }()
Ici, nous informons la vue de table que la hauteur de ligne doit avoir une valeur calculée automatiquement en fonction de son contenu.
Concernant la hauteur de ligne estimée:
«Fournir une estimation non négative de la hauteur des lignes peut améliorer les performances de chargement de la vue de table.» - Apple Docs
Dans
ViewDidLoad
nous devons recharger la vue de table pour que ces modifications prennent effet:
override func viewDidLoad() { super.viewDidLoad() ... DispatchQueue.main.async { self.tableView.reloadData() } }
Maintenant, allez-y et ajoutez une autre cellule, augmentez le nombre de lignes à quatre et ajoutez une autre
switch
à
cellForRow
:
case 3: cell.titleLabel.text = "Address" cell.descriptionLabel.text = "45, Walt Disney St.\n37485, Mickey Mouse State"
Maintenant, exécutez l'application et elle devrait ressembler à ceci:
Conclusion
Incroyable non? Et pour vous rappeler pourquoi nous codons réellement notre interface utilisateur, voici un article de blog entier rédigé par notre équipe mobile sur les raisons pour lesquelles nous
n'utilisons pas de storyboards dans Instabug .
Ce que vous avez fait dans deux parties de cette leçon:
- Suppression du fichier
main.storyboard
de votre projet. - Nous avons créé
UIWindow
programme et lui avons attribué un rootViewController
. - Création de divers éléments d'interface utilisateur dans le code, tels que des étiquettes, des vues d'image, des contrôles segmentés et des vues de tableau avec leurs cellules.
UINavigationBar
imbriqué dans votre application.- Création d'une taille dynamique
UITableViewCell
.