Un clone d’Instagram utilisant SwiftUI et GraphQL
Aujourd’hui, nous commençons une série d’articles de blog qui vous apprendront à utiliser un grand nombre d’outils sympas pour créer votre propre réseau social : une application qui ressemble à Instagram.
Nous ne ferons pas d’économies sur la technologie et nous utiliserons les derniers et les meilleurs : Parse, GraphQL, un peu de NodeJS et surtout le tout dernier framework d’Apple (qui n’est pas encore sorti) SwiftUI.
Cela prendra quelques posts afin d’être pleinement fonctionnel, mais quand nous y serons, vous réaliserez à quel point il peut être simple de faire fonctionner vos idées avec très peu d’effort ici à Back4app.
Pour un meilleur apprentissage, téléchargez le projet iOS Instagram Clone avec le code source.
Il semble donc qu’il soit temps de…
Contents
Voulez-vous un démarrage rapide ?
Clonez cette application depuis notre Hub et commencez à l’utiliser sans aucun problème !
Un peu d’introduction
Vous connaissez probablement déjà les énormes avantages que Parse apporte à votre processus de développement, surtout s’il est hébergé dans Back4app, car notre ensemble d’outils augmente la productivité de façon considérable.
Et nos derniers billets sur GraphQL vous ont montré à quel point il peut être facile de maintenir le développement des APIs dans le temps si vous l’utilisez. Si vous avez manqué ces articles, je vous suggère fortement de prendre votre temps pour les lire, car ils amélioreront grandement votre compréhension au fur et à mesure.
Vous pouvez en savoir plus sur GraphQL et nous avons également un article complet sur la façon de l’utiliser dans l’article Web API.
Vous pouvez également voir une certaine intégration avec NodeJS dans Cloud Code Functions en lisant GraphQL et NodeJS.
Il nous reste donc SwiftUI.
SwiftUI est, selon Apple, “un moyen innovant et exceptionnellement simple de construire des interfaces utilisateur sur toutes les plateformes Apple avec la puissance de Swift”.
Pour ma part, j’aime à penser que SwiftUI est plus que cela.
Si, comme moi, vous développez des logiciels depuis un certain temps, vous savez probablement que la communauté des développeurs a toujours été divisée entre la commodité de l’utilisation de Storyboards, l’interface de construction d’interfaces utilisateur par glisser-déposer d’Apple, et la puissance de l’interface utilisateur programmatique.
SwiftUI vient apporter le meilleur des deux mondes : elle est suffisamment facile à apprendre pour les débutants, tout en conservant la maintenabilité d’une interface utilisateur codée.
Combinez cela avec un rendu en temps réel, afin que les développeurs puissent facilement voir le résultat visuel de ce qu’ils codent, et faites-le dans les deux sens : si vous changez le code, cela se reflétera sur l’interface utilisateur, et si vous changez graphiquement l’interface utilisateur, cela se reflétera sur le code, et vous obtenez l’une des façons les plus puissantes et les plus faciles de fournir des interfaces utilisateur magnifiquement conçues, mais maintenables au fil du temps.
De quoi aurez-vous besoin ?
Au moment où j’écris cet article, Apple teste encore la version bêta de macOS Catalina, XCode 11, iOS 13 et quelques autres produits qui devraient bientôt arriver sur les étagères.
Bien que vous puissiez utiliser XCode 11 avec macOS Mojave, vous aurez besoin de Catalina pour rendre les aperçus de SwiftUI en temps réel.
Pas de Catalina ? Pas de prévisualisation pour vous !
C’est une décision que vous devez prendre car j’ai trouvé que le passage de Bash à ZSH comme shell officiel de macOS dans Catalina a cassé beaucoup de mes outils et scripts que j’utilise pour mon travail quotidien, j’ai décidé de rester dans Mojave pendant un certain temps. C’est à vous de décider si vous allez mettre à jour ou non.
Vous aurez également besoin d’un compte Back4app avec une application créée. Vous pouvez apprendre à créer votre première application dans la documentation de Back4app.
Enfin, nous utiliserons Apollo Client afin d’intégrer GraphQL à notre projet. Si vous n’avez pas d’expérience avec lui, j’ai écrit un article très détaillé sur la façon de le configurer et de l’utiliser pour l’authentification Web.
Avant de commencer, vous aurez besoin de :
- XCode 11 installé (avec macOS Catalina si vous voulez voir les Previews)
- Votre nouvelle application créée dans Back4app
- Apollo Client installé et configuré
Mon application s’appellera Back4Gram, car elle ressemblera à Instagram.
Notre toute première classe
Je vous explique toujours comment Parse crée la classe User, pour que vous puissiez inscrire de nouveaux utilisateurs et authentifier ceux qui existent déjà, et la classe Role, pour que vous puissiez regrouper des utilisateurs avec des privilèges similaires. C’est génial parce que, au moins pour l’authentification, nous n’avons pas besoin de créer de classe.
La classe User possède trois propriétés créées automatiquement, dont deux sont obligatoires et une facultative :
- nom d’utilisateur (obligatoire)
- mot de passe (obligatoire)
- email (optionnel)
Ces propriétés sont suffisantes pour que notre application puisse enregistrer et gérer les utilisateurs.
Comme vous le savez probablement, la politique d’Apple concernant le stockage des emails des utilisateurs est très stricte, je vous recommande donc de ne pas demander ou conserver ces informations ou vous pourriez ne pas être approuvé.
Comme il s’agit uniquement d’un tutoriel, je demanderai à l’utilisateur d’entrer ces informations, juste pour que vous puissiez voir comment cela fonctionne.
Classes d’utilisateurs et de rôles créées automatiquement
Comme si cela n’était pas assez utile, votre application nouvellement créée a déjà des requêtes GraphQL et des mutations automatiquement créées pour nous. Ceci est vrai pour nos classes créées automatiquement et aussi pour toute autre classe que vous créerez au fil du temps. Nous vous avons aussi couvert avec la documentation !
Vous ai-je dit que nous avions aussi une fonction d’auto-complétion ? Oui, nous l’avons !
Donc, ma première Mutation sera comme ci-dessus, et utilisera notre mutation spécifique signUp() pour créer un nouvel utilisateur :
La requête dépendra de la version de Parse que vous utilisez :
Parse 3.7.2 :
mutation signUpUser($username : String !, $password : String !, $email : String) { utilisateurs { signUp( champs : { username : $username, password : $password, email : $email } ) { objectId } } }
Parse 3.8 :
mutation signUpUser($username : String !, $password : String !, $email : String) { signUp( fields : { username : $username, password : $password, email : $email } ) { objectId } }
Parse 3.9 :
mutation signUpUser($username : String !, $password : String !, $email : String) { signUp( fields : { username : $username, password : $password, email : $email } ) { id } }
Remarquez que je rends mes variables username et password obligatoires en mettant un ” !” à la fin de leurs types, alors que email est optionnel, en n’ayant pas de ” !” après son type.
Au final, je retourne l’objectId de l’utilisateur nouvellement créé.
Et puisque nous en sommes là, codons également notre mutation GraphQL pour la connexion d’un utilisateur déjà existant.
Dans ce cas, le nom d’utilisateur et le mot de passe sont obligatoires, et je vais récupérer le sessionToken de l’utilisateur :
Parse 3.7.2
mutation logInUser($username : String !, $password : String !){ utilisateurs{ logIn(username : $username, password : $password){ sessionToken } } }
Parse 3.8
mutation logInUser($username : String !, $password : String !){ logIn(username : $username, password : $password){ sessionToken } }
Parse 3.9
mutation logInUser($username : String !, $password : String !){ logIn(username : $username, password : $password){ sessionToken } }
Enfin, une mutation permettant à l’utilisateur de se déconnecter, qui ne nécessite aucun paramètre et renvoie uniquement un booléen :
Parse 3.7.2
mutation logOutUser{ utilisateurs{ logOut } }
Parse 3.8
mutation logOutUser{ logOut }
Parse 3.9
mutation logOutUser{ logOut }
Ajoutez tous ces fichiers dans un nouveau fichier appelé UserMutations.graphql, sauvegardez également votre fichier schema.graphql que vous avez appris à télécharger dans notre article précédent.
Gardez ces fichiers en sécurité car nous les ajouterons à notre…
Projet SwiftUI
Créons notre projet SwiftUI.
Lancez XCode 11 et cliquez sur “Create a new XCode Project” dans la fenêtre Welcome to XCode.
Si vous n’obtenez pas cette fenêtre, vous pouvez toujours aller dans File > New > Project.
Choisissez iOS App en haut, puis Single View App. Donnez-lui un bon nom et n’oubliez pas de choisir Swift comme langage et SwiftUI comme interface utilisateur :
N’oubliez pas de choisir SwiftUI comme interface utilisateur.
Maintenant, installez le client Apollo comme vous l’avez appris dans notre article sur l’api web, et après cela, ajoutez les fichiers UserMutations.graphql et schema.graphql à votre projet.
Compilez et ajoutez le fichier API.swift nouvellement généré au projet.
A ce stade, je m’attends à ce que vous ayez :
- XCode 11 installé et en cours d’exécution
- Le client Apollo installé et intégré dans votre projet XCode
- Les fichiers UserMutations.graphql et schema.graphql ont été ajoutés à votre projet XCode.
- Fichier généré API.swift ajouté à votre projet XCode
Votre projet devrait ressembler à ceci :
Tout est intégré dans XCode 11
Nous sommes maintenant prêts à commencer le SwiftUI’ing.
Inscrivez-vous maintenant à Back4App et commencez à construire votre application Instagram Clone.
Décider de nos contrôles
Avant de pouvoir connecter ou déconnecter un utilisateur, nous devons d’abord créer cet utilisateur. Nous allons donc commencer par coder notre écran d’inscription.
En général, nous pouvons avoir 4 à 5 contrôles dans cet écran :
- Un type de texte pour indiquer à l’utilisateur de quel écran il s’agit (texte)
- Nom d’utilisateur (contrôle de saisie de texte)
- Mot de passe (contrôle de saisie de texte sécurisé)
- Confirmation du mot de passe (facultatif, dépend de la plateforme, contrôle de saisie de texte sécurisé)
- Courriel (contrôle de saisie de texte formaté)
- Bouton d’inscription (bouton)
Comme il s’agit d’une application mobile, j’ai décidé de ne pas mettre le contrôle de saisie de texte Confirm Password, car la plupart des plateformes mobiles permettent à l’utilisateur de voir le dernier caractère tapé et, dans la plupart des cas, cela suffit à l’utilisateur pour déterminer si le mot de passe a été tapé correctement.
Ces contrôles seront alignés verticalement, l’un en dessous de l’autre, dans l’ordre ci-dessus, chacun avec sa propre couleur.
Construction de l’interface utilisateur
Comme je l’ai mentionné précédemment, je n’utilise pas macOS Catalina et je n’aurai donc pas d’aperçus activés, mais je vais compiler et vous montrer les résultats avec mon code tel que je l’avais.
Créez une nouvelle vue SwiftUI en allant dans File > New > File et en choisissant SwiftUI View dans la section User Interface.
Nommez ce fichier SignUpView.swift et il apparaîtra intégré à votre projet.
Vous remarquerez que la vue nouvellement créée contient déjà un contrôle Text avec la chaîne “Hello World !”.
La beauté de Swift réside dans le fait que vous pouvez créer plusieurs vues simples, contenant uniquement les contrôles nécessaires à cette vue, puis fusionner plusieurs vues en une plus complexe. C’est l’orientation objet à son meilleur.
Nous avons décidé d’aligner nos contrôles verticalement et SwiftUI a un contrôle spécifique pour cela appelé VStack (Vertical Stack) : tout ce qui est à l’intérieur d’un VStack sera automatiquement empilé verticalement, donc, juste pour tester, ajoutons notre contrôle VStack à notre ContentView.swift et à l’intérieur de celui-ci, ajoutons deux fois le SignUpView :
Ajouté deux fois le SignUpView dans la VStack : il s’affiche deux fois aligné verticalement
Comme c’est cool ! Si nous avons besoin de plus de contrôles, nous pouvons simplement continuer à les ajouter !
Supprimons la VStack et la duplicité pour n’avoir qu’un seul SignUpView() dans cette vue, puis revenons à notre SignUpView et commençons à le modifier, car nous avons déjà vu les résultats de notre test. Votre ContentView devrait ressembler à ceci maintenant :
struct ContentView : View { var body : some View { SignUpView() } }
Nous savons déjà que nous aurons besoin de cette VStack avec Text en haut de l’écran, alors ajoutons simplement la VStack à la SignUpView et changeons la chaîne “Hello World !” en quelque chose d’utile, comme “Sign Up”. Notre SignUpView devrait ressembler à ceci :
struct SignUpView : View { var body : some View { VStack{ Text("Sign Up") } } }
Et puisque nous savons déjà quels contrôles nous devons ajouter en dessous, nous pouvons ajouter :
- un champ TextField pour le nom d’utilisateur
- un SecureField pour le mot de passe
- un champ TextField pour l’adresse électronique
- un bouton pour s’inscrire
Les TextFields et SecureFields sont assez similaires à l’ancien UITextField, mais doivent s’appuyer sur la liaison à l’état, nous allons donc les déclarer comme suit :
Control(“Placeholder”, text : stateValueToBindTo)
et la toute première étape consiste à déclarer l’état à lier :
@State var username : String = "" @State var password : String = "" @State var email : Chaîne = ""
Ceci étant dit, nous pouvons commencer à ajouter nos contrôles :
Text("S'inscrire") TextField("Nom d'utilisateur", texte : $username) SecureField("Mot de passe", texte : $password) TextField("Email (optional)", texte : $email)
Enfin, nous pouvons ajouter notre bouton, dont la structure est la suivante :
Button(action : {
//Code à exécuter en cas de déclenchement
}){
/Contenu
}
Comme vous l’avez probablement remarqué, il fonctionne tout autant qu’un UIButton, mais sa structure est beaucoup plus flexible car nous pouvons définir son contenu de manière programmatique, en y ajoutant pratiquement n’importe quoi : un texte, une image. Tout ce que vous voulez.
L’ancienne structure cible/action a disparu, remplacée par une nouvelle structure action:{} pour gérer le comportement en cas de pression.
Notre bouton contiendra un texte disant “Sign Up !”, alors ajoutons-le :
Button(action : { }){ Text("S'inscrire !") }
Votre code final devrait ressembler à ceci :
struct SignUpView : View { @State var username : String = "" @State var password : String = "" @State var email : String = "" var body : some View { VStack{ Text("S'inscrire") TextField("Nom d'utilisateur", texte : $username) SecureField("Mot de passe", texte : $password) TextField("Email (optionnel)", texte : $email) Button(action : { }){ Text("S'inscrire !") } } } }
Qu’en est-il de notre aperçu ?
Il semble prometteur, mais nous pouvons l’améliorer considérablement en définissant quelques propriétés visuelles.
Et s’il y a quelque chose de super sympa avec SwiftUI, c’est qu’on peut ajouter des propriétés visuelles au code et voir les changements en temps réel !
Essayons donc !
Tout d’abord, déclarons les couleurs que nous allons utiliser
let lightGreyColor = Color(red : 239.0/255.0, green : 243.0/255.0, blue : 244.0/255.0, opacity : 1.0) let lightBlueColor = Color(red : 36.0/255.0, green : 158.0/255.0, blue : 235.0/255.0, opacity : 1.0)
ajoutons ensuite quelques fonctionnalités intéressantes :
Un peu de remplissage afin d’avoir un peu d’espace entre ces éléments.
Définir les propriétés background et foregroundColor afin d’avoir des contrôles cohérents avec notre marque.
Un cornerRadius pour avoir des coins arrondis.
Et définir la police et le poids de la police pour rendre les choses plus agréables.
Voilà !
C’est joli ou quoi ?
Juste pour que vous puissiez suivre notre code à ce stade :
struct SignUpView : View { @State var username : String = "" @State var password : String = "" @State var email : String = "" let lightGreyColor = Color(red : 239.0/255.0, green : 243.0/255.0, blue : 244.0/255.0, opacity : 1.0) let lightBlueColor = Color(red : 36.0/255.0, green : 158.0/255.0, blue : 235.0/255.0, opacity : 1.0) var body : some View { VStack{ Text("S'inscrire") .font(.largeTitle) .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.bottom, 20) TextField("Nom d'utilisateur", text : $username) .padding() .background(lightGreyColor) .cornerRadius(5.0) .padding(.bottom, 20) SecureField("Password", texte : $password) .padding() .background(lightGreyColor) .cornerRadius(5.0) .padding(.bottom, 20) TextField("Email (optional)", texte : $email) .padding() .background(lightGreyColor) .cornerRadius(5.0) .padding(.bottom, 20) Button(action : { }){ Text("S'inscrire !") .font(.headline) .foregroundColor(.white) .padding() .frame(width : 220, height : 60) .background(lightBlueColor) .cornerRadius(15.0) } }.padding() } }
Notre bel écran brillant est presque prêt.
Que diriez-vous de…
Quelques fonctionnalités
Maintenant que notre interface est prête, il est temps d’ajouter nos appels Apollo à nos méthodes GraphQL.
Comme nous allons utiliser ces appels dans presque toutes les vues, nous devons créer un client Apollo à un endroit où toutes les vues peuvent y accéder. Cet endroit est le AppDelegate.swift.
Déclarez votre client Apollo à cet endroit, en dessous de l’élément
import UIKit
et le début du bloc de code
@UIApplicationMain class AppDelegate : UIResponder, UIApplicationDelegate { ...
Comme nous vous l’avons montré dans notre précédent article, toutes les vues peuvent désormais effectuer des requêtes et des mutations GraphQL.
Vous devez d’abord importer le framework Apollo et ensuite instancier votre client comme ceci :
import Apollo // Initialisation du client Apollo. // Plus d'informations ici : https://www.back4app.com/docs/ios/swift-graphql let apollo : ApolloClient = { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = [ "X-Parse-Application-Id" : "YourAppIdHere", "X-Parse-Client-Key" : "YourClientKeyHere" ] let url = URL(string : "https://parseapi.back4app.com/graphql") ! return ApolloClient( networkTransport : HTTPNetworkTransport( url : url, configuration : configuration ) ) }()
Il suffit de remplacer les chaînes YourAppIdHere et YourClientKeyHere par vos valeurs actuelles et nous pouvons continuer.
Comme vous l’avez probablement deviné, nous devons effectuer notre mutation pour SignUp lorsque nous cliquons sur le bouton SignUp, donc, dans le bloc d’action, appelons-le :
// Exécuter la mutation SignUpUser, en passant les paramètres que nous venons d'obtenir de nos TextFields apollo.perform(mutation : SignUpUserMutation(username : self.username, password : self.password, email : self.email)){ result in // Commutons le résultat afin de pouvoir distinguer un résultat positif d'un résultat erroné switch result { // En cas de succès case .success(let graphQLResult) : // Nous essayons d'Parse notre résultat if let objId = graphQLResult.data ?.users ?.signUp.objectId { print ("Utilisateur créé avec ObjectId : " + objId) } // mais en cas d'erreur GraphQL, nous affichons ce message else if let errors = graphQLResult.errors { // erreurs GraphQL print(errors) } // En cas d'échec, nous présentons ce message case .failure(let error) : // Erreurs de réseau ou de format de réponse print(error) } }
Cela fonctionnera, mais ces méthodes print() n’afficheront que la console. Nous avons besoin de présenter une alerte à notre utilisateur, alors changeons-la pour une alerte, avec la structure suivante :
Alert(title : Text(“Title”), message : Text(“Message”), dismissButton : .default(Text(“TextOfButton”))
Comme nous aurons besoin de modifier le titre et le message au cours de l’exécution, nous devons définir une structure pour gérer ces valeurs, car les variables propres ne peuvent pas être modifiées au cours de l’exécution :
struct Message { var alertTitle : String = "" var alertText : String = "" } var myMessage = Message()
Nous créons également un état pour que la vue puisse savoir quand afficher l’alerte :
@State private var showingAlert = false
Le code complet de l’action de notre bouton devrait ressembler à ceci :
Button(action : { // Effectuer la mutation SignUpUser, en passant les paramètres que nous venons d'obtenir de nos TextFields apollo.perform(mutation : SignUpUserMutation(username : self.username, password : self.password, email : self.email)){ result in // Commutons le résultat afin de pouvoir distinguer un résultat positif d'un résultat erroné switch result { // En cas de succès case .success(let graphQLResult) : // Nous essayons d'Parse notre résultat if let objId = graphQLResult.data ?.users ?.signUp.objectId { myMessage.alertTitle = "Yay !" myMessage.alertText = "L'utilisateur s'est inscrit !" self.showingAlert = true print ("Utilisateur créé avec ObjectId : " + objId) } // mais en cas d'erreur GraphQL, nous affichons ce message else if let errors = graphQLResult.errors { // erreurs GraphQL myMessage.alertTitle = "Oops !" myMessage.alertText = "Nous avons une erreur GraphQL : " + errors.description self.showingAlert = true print(errors) } // En cas d'échec, nous présentons ce message case .failure(let error) : // Erreurs de réseau ou de format de réponse myMessage.alertTitle = "Oops !" myMessage.alertText = "Nous avons eu une erreur : " + error.localizedDescription self.showingAlert = true print(error) } } }){ Text("S'inscrire !") .font(.headline) .foregroundColor(.white) .padding() .frame(width : 220, height : 60) .background(lightBlueColor) .cornerRadius(15.0) } .alert(isPresented : $showingAlert) { Alert(title : Text(myMessage.alertTitle), message : Text(myMessage.alertText), dismissButton : .default(Text("OK"))) }
Ça a l’air prometteur, hein ?
Alors iiiiiiiii c’est tiiiiiime
Compilez et exécutez. Essayez d’inscrire un nouvel utilisateur et vous devriez obtenir…
Voyons si l’utilisateur est créé dans notre Parse Dashboard !
Conclusion
Vous avez créé une vue SwiftUI qui fonctionne et qui effectue des mutations GraphQL en utilisant Apollo Client. C’est vraiment génial !
Nous allons continuer à travailler sur notre clone d’Instagram ! Les prochaines étapes sont la connexion et la déconnexion et le post est déjà dans le four !
Restez à l’écoute !
Référence
- La première partie de cette série est Instagram Clone.
- La partie 2 est Connexion Instagram.
- La partie 3 est Profil Instagram.
- La partie 4 est Instagram HomeView.
- Voir le backend de l’application à iOS Instagram Clone Backend.
- Téléchargez un projet iOS Instagram Clone avec le code source et commencez à utiliser Back4App.
Inscrivez-vous maintenant à Back4App et commencez à construire votre application Instagram Clone.