Tout ce que vous vouliez savoir sur SwiftUI mais aviez peur de demander


Salut Je m'appelle Renat, je développe un service d'analyse d'abonnement sur iOS - Apphud.


Comme vous le savez, Apple lors de la WWDC 2019 a présenté son nouveau cadre SwiftUI, qui est conçu à l'avenir pour remplacer (ou non?) Le UIKit familier. SwiftUI vous permet de décrire l'interface de l'application dans un style déclaratif et réduit considérablement la quantité de code.


Apple a déjà introduit quelques tutoriels en anglais intéressants avec de nombreux exemples. J'essaierai de parler du nouveau cadre sous forme de questions et réponses. Alors allons-y.


Avant de commencer


Pour travailler avec SwiftUI, vous devez télécharger Xcode 11 Beta . Vous devez également être un développeur Apple enregistré. Avoir la dernière version de macOS Catalina est souhaitable, mais pas nécessaire. Sans cela, Canvas ne sera pas disponible.


Donc, dans Xcode 11 Beta, créez un nouveau projet et assurez-vous que «Use SwiftUI» est coché.


Q & A


Où est passé Interface Builder?


SwiftUI n'a plus besoin d' Interface Builder - il a été remplacé par Canvas , un éditeur d'interface interactif étroitement lié au code. Lors de l'écriture de code, sa partie visuelle dans le canevas est générée automatiquement et vice versa. Très pratique et surtout sûr. Maintenant, votre application ne se @IBOutlet pas car vous avez oublié de mettre à jour l' @IBOutlet avec la variable. Dans cet article, nous ne toucherons pas à la toile , nous ne considérerons que le code.


Le lancement de l'application a-t-il changé?


Oui, maintenant l'objet initial dans l'interface d'application n'est plus UIWindow , mais la nouvelle classe UIScene (ou son UIWindowScene descendant). Et une fenêtre est ajoutée à la scène. Ces changements affectent non seulement SwiftUI, mais iOS 13 dans son ensemble.


Lorsque vous créez un projet, vous verrez les fichiers AppDelegate , SceneDelegate et ContentView . SceneDelegate - un délégué de la classe UIWindowScene , utilisé pour contrôler les scènes dans l'application. Rappelle fortement AppDelegate .


La classe SceneDelegate est spécifiée dans Info.plist
La classe SceneDelegate est spécifiée dans Info.plist


Dans la scene: willConnectTo: options: méthode déléguée scene: willConnectTo: options: crée une fenêtre et un UIHostingController racine, qui contient un ContentView . ContentView est notre page d'accueil. Tout le développement sera effectué dans cette classe.


En quoi View est-il différent de UIView?


ContentView.swift vous ouvrez ContentView.swift , vous verrez une déclaration de conteneur ContentView. Comme vous l'avez déjà compris, il n'y a pas de viewDidLoad familières viewDidLoad ou viewDidAppear viewDidLoad viewDidAppear . La base des écrans ici n'est pas l' UIViewController , mais la vue . La première chose à noter est que ContentView est une struct qui accepte le protocole View . Oui, View maintenant devenu un protocole, et très simple. La seule méthode que vous devez implémenter dans ContentView est de décrire le body la variable. Toutes vos sous - vues et vues personnalisées doivent accepter le protocole View , c'est-à-dire doivent avoir une variable de body .


Qu'est-ce que le corps?


Body est directement notre conteneur où toutes les autres sous- vues sont ajoutées. Ceci est quelque peu similaire au corps d'une page html , où la page html est un ContentView . Body doit toujours avoir exactement un descendant et toute classe qui accepte le protocole View .


 struct ContentView: View { var body: some View { Text("Hello, world!") } } 

Types de retour opaques ou quels sont certains?


La construction de some TypeName est une innovation Swift 5.1 appelée le type de retour opaque . Il est utilisé pour les cas où il n'est pas important pour nous quel objet renvoyer, l'essentiel est qu'il prend en charge le type spécifié, dans ce cas, le protocole View .


Si nous écrivions simplement var body: View , cela signifierait que nous devrions retourner la View . La classe Any ne fonctionne pas non plus, car il faudrait effectuer une opération de conversion de type (en utilisant l'opérateur as! ). Par conséquent, ils ont trouvé le mot spécial some devant le nom du protocole pour indiquer le type de retour opaque . Au lieu de View nous pouvons renvoyer Text , Image , VStack - n'importe quoi, car ils prennent tous en charge le protocole View . Mais il doit y avoir exactement un élément: lorsque vous essayez de renvoyer plusieurs View compilateur renvoie une erreur.



Erreur de compilation lors de la tentative de retour de plusieurs éléments dans le corps


Quelle est la syntaxe à l'intérieur des crochets et où est addSubview?


Swift 5.1 a introduit la possibilité de regrouper des objets en un seul ensemble dans un style déclaratif. Ceci est similaire à un tableau à l'intérieur d'un bloc de fermeture , mais les éléments sont énumérés à partir d'une nouvelle ligne sans virgule et retour . Ce mécanisme s'appelait Function Builder .


Ceci est largement utilisé dans SwiftUI. Basé sur Function Builder, ils ont créé ViewBuilder - un concepteur d'interface déclarative. En utilisant ViewBuilder nous n'avons plus besoin d'écrire addSubview pour chaque élément - il suffit de lister toutes les View d'une nouvelle ligne à l'intérieur du bloc de fermeture . SwiftUI ajoutera et groupera des éléments dans un conteneur parent plus complexe.


 @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) @_functionBuilder public struct ViewBuilder { /// Builds an empty view from an block containing no statements, `{ }`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through /// unmodified. public static func buildBlock<Content>(_ content: Content) -> Content where Content : View } 

Annonce ViewBuilder dans le cadre SwiftUI


Comment ajouter UILabel, UIImageView et d'autres éléments?


Les éléments sont créés très simplement: chaque View doit être écrite à partir d'une nouvelle ligne et changer l'apparence à l'aide des fonctions de modification ( modificateurs de vue ). La différence entre les modificateurs et les fonctions que nous connaissons est qu'ils retournent toujours un objet conteneur au lieu de void . Par conséquent, nous pouvons créer des chaînes entières de modificateurs via le point.


 var body: some View { VStack{ Text("World Time").font(.system(size: 30)) Text("Yet another subtitle").font(.system(size: 20)) } } 

Cependant, tous les contrôles et View n'ont pas leurs analogues dans SwiftUI. Voici une liste partielle des classes d'UIKit et de leurs analogues:


  • UITableView -> List


  • UICollectionView n'a pas d'analogue


  • UILabel -> Text


  • UITextField -> TextField


  • UIImageView -> Image


  • UINavigationController -> NavigationView


  • UIButton -> Button


  • UIStackView -> HStack / VStack


  • UISwitch -> Toggle


  • UISlider -> Slider


  • UITextView n'a pas d'analogue


  • UIAlertController -> Alert / ActionSheet


  • UISegmentedControl -> SegmentedControl


  • UIStepper -> Stepper


  • UIDatePicker -> DatePicker



Comment se passe la navigation entre les écrans?


Le contrôleur de navigation joue le rôle d'une NavigationView spéciale. Emballez simplement votre code dans NavigationView{} . Et l'action de transition elle-même peut être ajoutée à un bouton spécial NavigationLink , qui pousse l'écran conditionnel DetailView .


 var body: some View { NavigationView { Text("World Time").font(.system(size: 30)) NavigationLink(destination: DetailView() { Text("Go Detail") } } } 

Comment présenter de nouvelles vues de façon modale? Cela se fait, par exemple, en utilisant la construction de feuille :


 Button(action: { print("Button Pushed") self.show_modal = true }) { Text("Present Modal") }.sheet(isPresented: self.$show_modal) { ModalView() } 

Comme mentionné ci-dessus, body peut renvoyer non seulement une instance de View , mais aussi toute autre classe qui accepte ce protocole. Cela nous donne la possibilité de DetailView non pas DetailView , mais même le Text ou l' Image !


Comment organiser les éléments à l'écran?


Les éléments sont disposés indépendamment les uns des autres et peuvent être situés verticalement à l'intérieur de VStack , horizontalement HStack et l'un au-dessus d'un autre ZStack . ScrollView et ListView sont également disponibles pour nous. Vous pouvez alterner et partager ces conteneurs pour obtenir n'importe quel maillage d'éléments.


En combinant les conteneurs les uns avec les autres, vous pouvez obtenir un arbre assez grand avec un grand nombre de pièces jointes. Cependant, SwiftUI est optimisé spécifiquement pour cela, donc l'imbrication profonde des conteneurs n'affecte pas les performances. Ceci est indiqué dans la vidéo avec wwdcpartir de 15:32 ).


 var body: some View { NavigationView { VStack { NavigationLink(destination: LargeView(timeString: subtitle)) { Text("See Fullscreen") } Text("World Time").font(.system(size: 30)) } } } 

Comment afficher la barre de navigation?


Déclarer une NavigationView ne suffit pas; vous devez spécifier un titre et un style de navigation pour la barre de navigation .


 NavigationView { VStack{}.navigationBarTitle(Text("World Time"), displayMode: .inline) } 

Notez que la fonction navigationBarTitle n'est pas appelée sur NavigationView , mais sur sa View interne. DisplayMode est un paramètre qui indique le style de la barre de navigation : grand ou standard.


Existe-t-il un analogue de la méthode viewDidLoad?


Si vous souhaitez exécuter du code lors de l'initialisation de la View , vous pouvez le faire en ajoutant la fonction onAppear {}. OnAppear peut être ajouté à n'importe quelle View , par exemple, à VStack . Dans cet exemple, lorsque le conteneur apparaît à l'écran, une requête http est envoyée au serveur.


 struct ContentView : View { @State var statusString : String = "World Time" var body: some View { NavigationView { VStack { NavigationLink(destination:DetailView()) { Text("Go Detail") } Text(statusString).font(.system(size: 30)) }.onAppear { self.loadTime() } } } func loadTime(){ NetworkService().getTime { (time) in if let aTime = time { self.statusString = "\(aTime.date())" } } } } 

Nous appelons la fonction loadTime , qui demande l'heure actuelle au serveur et renvoie le modèle WorldTime . Nous n'entrerons pas dans les cycles de la classe NetworkService , vous pouvez regarder tout le code, après avoir téléchargé les codes source. Lien à la fin de l'article.

La variable var statusString été rendue afin de lui attribuer l'heure actuelle plus tard. La variable a un attribut spécial @State . Que veut-il dire?


Property Wrappers ou qu'est-ce que @State ?


Swift 5.1 a introduit les soi-disant wrappers de propriété (ou délégués de propriété ). Dans SwiftUI, les wrappers de propriété sont utilisés pour mettre à jour ou lier l'un des paramètres d' affichage avec notre propre variable, par exemple, la valeur du commutateur à bascule .


L'attribut @State est un attribut spécial qui est placé avant une déclaration de variable. Cela nous permet de suivre automatiquement les changements de propriété sans code supplémentaire. Dans l'exemple ci-dessus, le texte «Heure mondiale» passera à la date actuelle dès que nous mettrons à jour la valeur statusString.


Pour lier des valeurs ( Properties Binding ), nous pouvons spécifier un caractère spécial $ avant le nom de la variable dans le code lui-même:


 struct DetailsView: View { @State var changeToggle: Bool var body: some View { Toggle(isOn: $changeToggle) { Text("Change Toggle") } } } 

En changeant la position du commutateur, la valeur de la variable changera également.


Les wrappers de propriété sont un composant très important de SwiftUI, je viens de les mentionner en passant. Pour une connaissance plus détaillée des wrappers de propriétés, regardez la vidéo de wwdc ici (à partir de la 37e minute), ici (à partir de la 12e minute) et ici (à partir de la 19e minute).


Comment ajouter une vue à l'exécution?


Il convient de noter immédiatement que vous ne pouvez pas ajouter de vue à tout moment au sens littéral du terme. SwiftUI est un cadre déclaratif qui rend la vue entière. Cependant, vous pouvez définir diverses conditions à l'intérieur du corps et mettre à jour l'état de la vue lorsqu'elles changent. Dans cet exemple, nous utilisons le bouquet le plus simple de @State – if avec la variable isTimeLoaded .


 struct ContentView : View { @State var statusString : String = "World Time" @State var isTimeLoaded : Bool = false var body: some View { NavigationView { VStack { if isTimeLoaded { addNavigationLink() } Text(statusString).font(.system(size: 30)).lineLimit(nil) }.navigationBarTitle(Text("World Time"), displayMode: .inline) }.onAppear { self.loadTime() } } func addNavigationLink() -> some View { NavigationLink(destination: Text("124!!!")) { Text("Go Detail") } } func loadTime(){ NetworkService().getTime { (time) in if let aTime = time { self.statusString = "\(aTime.date().description(with: Locale.current))" self.isTimeLoaded = true } } } } struct DetailView : View { var timeString : String var body : some View { Text(timeString).font(.system(size: 40)).lineLimit(nil) } } 

Au fait, avez-vous remarqué que la fonction addNavigationLink() pas le mot return ? C'est une autre innovation de Swift 5.1 - pour les fonctions avec une seule expression, il est désormais facultatif d'écrire return . Mais vous pouvez écrire.


Conclusion


Ce n'est qu'une partie des questions et réponses de SwiftUI J'ai examiné les problèmes généraux, j'espère que cet article aidera les débutants à comprendre les principaux points de ce cadre. SwiftUI est encore brut, mais il sera sans aucun doute amélioré.


La question logique est: vaut-il la peine d'apprendre UIKit? Bien sûr que oui . UIKit est la base de la programmation sur iOS et il se développera davantage. De plus, de nombreux composants SwiftUI sont un wrapper sur UIKit. Eh bien, jusqu'à présent, il n'y a pas de bibliothèques, de cadres, de pods pour SwiftUI. Tout devra être écrit par vous-même. Il est donc préférable d'étudier les deux approches du développement - vous serez donc un développeur plus précieux.


Vous pouvez télécharger les sources du projet ici .


Merci d'avoir lu l'article jusqu'au bout. J'espère que vous le trouverez utile.


Que lire?


Source: https://habr.com/ru/post/fr455970/


All Articles