En juin, nous avons entendu parler de
SwiftUI pour la première fois - une toute nouvelle façon de créer et de travailler avec des éléments d'interface utilisateur dans les applications iOS et macOS (également iPadOS). C'était comme Noël en été. C'est nouveau, c'est déclaratif, c'est sexy! Et maintenant, quelques semaines seulement après la sortie d'iOS 13, nous pouvons commencer à utiliser SwiftUI dans tous nos projets. Apprenons à utiliser cet incroyable outil qu'Apple nous a donné, pour créer les classiques cartes magnétiques Tinder.
Dans cet article, je voudrais vous montrer comment obtenir une vue et un comportement de carte de type Tinder (glisser vers l'action), avec seulement quelques lignes de code.
Pour y parvenir, nous devons faire les choses suivantes, dans l'ordre:
- Créer UserView
- Créer une vue de navigation
- Créer BottomBarView
- Créer un swipeview
- Mettez tout cela ensemble dans ContentView
Commençons donc.
Présentation générale
UserView est construit à partir de deux sous-vues, l'une est
NameView qui contient le nom d'utilisateur, l'âge et les loisirs, et la seconde vue est juste une vue d'avatar qui affiche la photo de profil de l'utilisateur.
struct NameView: View { let name: String let age: Int let hobby: String var body: some View { VStack(alignment: .leading) { Spacer() Text("\(name), \(age)") .font(.title) .fontWeight(.semibold) .foregroundColor(.white) Text(hobby) .font(.system(size: 16)) .fontWeight(.regular) .foregroundColor(.white) } .padding() } }
Tout d'abord, nous devons définir le
NameView , cela représentera le nom d'utilisateur, l'âge et le passe-temps.
NameView est conforme au protocole
View , qui est utilisé pour définir des vues personnalisées dans SwiftUI. Le protocole de vue n'a qu'une seule exigence et c'est la définition de la propriété body qui doit retourner la structure de la vue et décrit son comportement. Vous pouvez en savoir plus sur le protocole
View dans la
documentation officielle d'
Apple .
Décomposons les objets que nous utilisons pour définir cette
vue :
- VStack qui agit comme un conteneur pour tous les objets en les alignant verticalement
- Espaceur qui indique à SwiftUI que cette vue doit être alignée en bas
- Texte qui représente l'étiquette avec nom et âge, avec les propriétés suivantes:
- Deuxième objet texte ayant des propriétés similaires et affichant le passe-temps de l'utilisateur
Veuillez noter que nous n'utilisons pas de déclaration de retour ici, à l'intérieur de la propriété body, mais à la place, nous renvoyons un VStack. SwiftUI utilise la proposition omit-return implémentée dans Swift 5.0. Vous pouvez en savoir plus
ici .
Avatarview
Voici comment
AvatarView est défini:
struct AvatarView: View { let image: UIImage var body: some View { Image(uiImage: image) .resizable() .overlay( Rectangle() .fill(LinearGradient(gradient: Gradient(colors: [.clear, .black]), startPoint: .center, endPoint: .bottom)) .clipped() ) .cornerRadius(12.0) } }
Plongeons-nous dans les composants qui composent cette unité d'avatar:
- Image - qui affiche l'image de l'utilisateur
- redimensionnable - cette méthode spécifie que l'image doit être redimensionnée pour s'adapter à l'endroit où elle est intégrée
- superposition (rectangle) - ici, nous définissons un dégradé qui sera un joli fond pour NameView, ce dégradé commence au centre de l'image et se termine en bas, il a une couleur claire au début et du noir en bas
- cornerRadius - l'image aura un rayon en coin
Et maintenant, incorporons ces deux vues dans une seule vue de conteneur, appelée
UserView .
Présentation générale
struct UserView: View { let userModel: UserModel var body: some View { ZStack(alignment: .leading) { AvatarView(image: userModel.image) NameView(name: userModel.name, age: userModel.age, hobby: userModel.hobby) } .shadow(radius: 12.0) .cornerRadius(12.0) } }
Voici ce qui se passe:
- ZStack - Il s'agit d'une vue de pile qui alignera ses enfants sur le même axe. Vous pouvez en savoir plus sur ZStack ici
- AvatarView - Notre vue d'avatar contenant l'image fournie via UserModel
- NameView - Notre vue de nom affichant le nom en fonction du modèle utilisateur
Après toutes ces étapes, exécutez l'application. Vous obtiendrez l'écran suivant:
Ajoutons maintenant une petite méthode d'aide. Avant de vous montrer comment est défini NavigationView, créons une méthode d'assistance, qui ressemble à ceci:
struct ViewFactory { static func button(_ name: String, renderingMode: Image.TemplateRenderingMode = .original) -> some View { Button(action: {}) { Image(name) .renderingMode(renderingMode) } } }
Ici, nous avons défini une méthode de fabrique de boutons, qui crée un nouveau bouton à partir d'une image donnée et d'un mode de rendu. Il n'y a pas de gestionnaire d'actions, car cela n'entre pas dans le cadre de cet article.
Navigationview
struct NavigationView: View { var body: some View { HStack { ViewFactory.button("profile_icon") Spacer() ViewFactory.button("fire_icon") .scaleEffect(2) Spacer() ViewFactory.button("chat_icon") } } }
SwiftUI créera automatiquement les
espaceurs de largeur égale et nous donnera la vue de navigation suivante:
Bottombarview
struct BottomBarView: View { var body: some View { HStack { ViewFactory.button("back_icon", renderingMode: .template) .foregroundColor(.orange) .background( GeometryReader { geometry in Circle() .offset(x: 2.5) .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 1.5, height: geometry.size.height * 1.5) } ) Spacer() ... }
Dans l'extrait de code ci-dessus, nous avons défini le premier bouton de notre vue de barre. Voici ce qui se passe:
- ViewFactory.button - ici, nous utilisons notre méthode d'assistance pour définir un bouton avec une image avec renderMode .template qui vous permet de mettre une couleur personnalisée pour cette image
- .foregroundColor - définir la couleur de notre vue
- .background - cette méthode définit la vue d'arrière-plan de l'objet donné
- GeometryReader - une vue de conteneur qui définit son contenu en fonction de sa propre taille et de son espace de coordonnées. Nous l'utilisons pour obtenir la taille actuelle d'un bouton et définir le cercle d'arrière-plan avec le cadre donné. En savoir plus sur les lecteurs de géométrie ici .
- Cercle - définit la forme d'arrière-plan
- .offset - cercle offset de l'axe x
- .foregroundColor - couleur de teinte du cercle
- .shadow - ombre du cercle
- .frame - définit le cadre du cercle en utilisant la taille du lecteur de géométrie (ici, nous définissons un cercle d'arrière-plan, 1,5 fois plus grand que le bouton actuel)
Maintenant, implémentons le reste des boutons:
struct BottomBarView: View { var body: some View { HStack { ViewFactory.button("back_icon", renderingMode: .template) .foregroundColor(.orange) .background( GeometryReader { geometry in Circle() .offset(x: 2.5) .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 1.5, height: geometry.size.height * 1.5) } ) Spacer() ViewFactory.button("close_icon", renderingMode: .template) .foregroundColor(.red) .background( GeometryReader { geometry in Circle().foregroundColor(.white) .frame(width: geometry.size.width * 2, height: geometry.size.height * 2) .shadow(color: .gray, radius: 12) } ) Spacer() ViewFactory.button("approve_icon", renderingMode: .template) .foregroundColor(.green) .background( GeometryReader { geometry in Circle() .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 2, height: geometry.size.height * 2) } ) Spacer() ViewFactory.button("boost_icon", renderingMode: .template) .foregroundColor(.purple) .background( GeometryReader { geometry in Circle() .foregroundColor(.white) .shadow(color: .gray, radius: 12) .frame(width: geometry.size.width * 1.5, height: geometry.size.height * 1.5) } ) } .padding([.leading, .trailing]) } }
Et en conséquence, nous avons maintenant cette belle vue:
Swipeview
Cette section est pour SwiftUI plus avancé. C'est vraiment là que les choses deviennent intéressantes. Nous aimerions implémenter le geste de balayage sur la vue d'action. Ce comportement est un bon cas d'utilisation pour un PageViewController, mais ce contrôleur de vue sera bientôt historique, c'est donc là que nous pouvons montrer la puissance réelle de SwiftUI.
Voyons donc comment SwipeView est implémenté:
struct SwipeView: View { @State private var offset: CGFloat = 0 @State private var index = 0 let users = [...] let spacing: CGFloat = 10 var body: some View { GeometryReader { geometry in return ScrollView(.horizontal, showsIndicators: true) { HStack(spacing: self.spacing) { ForEach(self.users) { user in UserView(userModel: user) .frame(width: geometry.size.width) } } } .content.offset(x: self.offset) .frame(width: geometry.size.width, alignment: .leading) .gesture( DragGesture() .onChanged({ value in self.offset = value.translation.width - geometry.size.width * CGFloat(self.index) }) .onEnded({ value in if -value.predictedEndTranslation.width > geometry.size.width / 2, self.index < self.users.count - 1 { self.index += 1 } if value.predictedEndTranslation.width > geometry.size.width / 2, self.index > 0 { self.index -= 1 } withAnimation { self.offset = -(geometry.size.width + self.spacing) * CGFloat(self.index) } }) ) } } }
Ici, nous avons utilisé quelques nouveaux concepts SwiftUI intéressants:
- @ State - Une valeur persistante d'un type donné, à travers laquelle une vue lit et surveille la valeur, ce qui signifie que chaque fois que cette propriété changera, la vue sera rechargée pour s'adapter à la mise à jour d'état donnée. Vous pouvez en savoir plus sur l'État ici .
- DragGesture - cet objet sera utilisé pour reconnaître chaque balayage effectué par l'utilisateur à l'écran. Vous pouvez en savoir plus à ce sujet ici: developer.apple.com/documentation/swiftui/draggesture
- @ State private var offset: CGFloat = 0 - cette propriété sera utilisée pour définir le décalage actuel de la vue de défilement lorsque les utilisateurs glissent sur l'écran
- @ State private var index = 0 - cette propriété définit quelle vue utilisateur est actuellement à l'écran
- ScrollView - vue de défilement horizontal sans indicateurs, qui sera un conteneur pour notre vue utilisateur
- HStack - vue horizontale de la pile qui contient toutes les vues utilisateur
- content.offset (self.offset) - il crée une connexion entre l'état de décalage et le décalage de contenu de la vue de défilement. Cela signifie que chaque fois que la propriété offset change, l'offset de la vue de défilement est également mis à jour
Nous
énumérons les utilisateurs existants en créant une
UserView pour chaque élément:
- .frame - ici, nous définissons le cadre de la vue de défilement qui doit s'adapter à la largeur de l'écran et doit être correctement aligné sur son conteneur
- .gesture - ici nous ajoutons notre objet DragGesture
DragGesture est un peu compliqué, mais néanmoins, il ajoute toute la logique de pagination en quelques lignes de code.
Décomposons DragGesture :
- onChanged () - ce bloc est invoqué chaque fois que l'utilisateur démarre et est en temps de geste de stagnation, nous calculons ici le décalage actuel de la vue utilisateur qui suit le doigt de l'utilisateur
- onEnded () - ici, nous sommes informés de la fin du mouvement de glissement, ici nous devons calculer si l'utilisateur souhaite glisser cette vue (gauche ou droite), ou peut-être que ce geste a été marqué, et l'utilisateur souhaite rester sur cet écran
- withAnimation - cette fermeture est invoquée avec animation, et permet de changer le décalage en douceur
Affichage du contenu
struct ContentView: View { var body: some View { VStack { NavigationView() .padding(.bottom) SwipeView() .padding(.bottom) BottomBarView() } .padding() } }
Notre vue de contenu est extrêmement simple à ce stade - elle compose toutes les vues créées précédemment, à l'intérieur d'une pile verticale (
VStack ). Pour le
NavigationView et le
SwipeView, nous avons ajouté un rembourrage par défaut en bas, et l'ensemble du
VStack a des rembourrages ajoutés à tous les bords.
Voilà. Terminé Voici à quoi ressemble notre application maintenant:
Réflexions finales
Comme nous pouvons le voir, SwiftUI est un outil très puissant, il nous donne un moyen facile de définir et de manipuler l'interface utilisateur dans un code déclaratif court.
Les développeurs de
React Native reconnaîtraient immédiatement ce paradigme déclaratif.
Mais rappelez-vous: SwiftUI est toujours en cours de développement et peut être extrêmement instable pour l'instant. Si vous souhaitez vérifier toute la base de code pour ce projet, vous pouvez le trouver sur
Github .
Si vous avez des réflexions ou des questions sur SwiftUI, n'hésitez pas à les partager dans des commentaires. De plus, si vous avez apprécié cet article, partagez-le avec votre communauté pour nous aider à passer le mot!