Un clone d’Instagram utilisant SwiftUI et GraphQL

Un clone d’Instagram utilisant SwiftUI et GraphQL
Un clone d'Instagram utilisant SwiftUI et GraphQL

Aujourd’hui, nous commençons une série d’articles de blog qui vous apprendront à utiliser de nombreux outils sympas pour construire votre propre réseau social : une application qui ressemble à Instagram.

Nous n’économiserons pas sur la technologie et utiliserons les plus récentes et les plus performantes : Parse, GraphQL, quelques NodeJS et surtout le (pas encore publié) dernier framework d’Apple, SwiftUI. 

Cela prendra quelques articles pour être pleinement fonctionnel, mais quand nous y serons, vous réaliserez à quel point il peut être simple de mettre en œuvre vos idées avec très peu d’efforts avec Back4app.

Pour un meilleur apprentissage, téléchargez le projet iOS Instagram Clone avec son code source.

Il est donc temps.

Voulez-vous un démarrage rapide ?

Clonez cette application à partir de notre Hub et commencez à l’utiliser sans aucun problème !

Quelques mots d’introduction

Vous connaissez probablement déjà les avantages considérables que Parse apporte à votre processus de développement, surtout s’il est hébergé dans Back4app, car notre ensemble d’outils augmente considérablement la productivité.

Nos derniers articles sur GraphQL vous ont montré combien il peut être facile de maintenir le développement des API au fil du temps si vous l’utilisez.

Si vous avez manqué ces articles, je vous suggère fortement de prendre votre temps et de les lire, car ils amélioreront grandement votre compréhension au fur et à mesure.

Vous pouvez en savoir plus sur GraphQL ici et nous avons également un article complet sur la façon de l’utiliser ici.

Vous pouvez également voir une certaine intégration avec NodeJS dans les fonctions de code cloud ici.

Il nous reste donc SwiftUI.

SwiftUI est, selon Apple, « une manière innovante et exceptionnellement simple de construire des interfaces utilisateurs sur toutes les plateformes Apple grâce à la puissance de Swift ».

J’aime moi-même penser que SwiftUI est bien 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é d’utiliser Storyboards, l’interface de création d’interface utilisateur par glisser-déposer d’Apple ou la puissance de l’interface utilisateur programmatique.

SwiftUI vient apporter le meilleur des deux mondes : il est assez 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 répercutera sur l’interface utilisateur, et si vous changez graphiquement l’interface utilisateur, cela se répercutera sur le code, et ce que vous obtiendrez est l’un des moyens les plus puissants et les plus faciles de fournir des interfaces utilisateur magnifiquement conçues, maintenables dans le temps.

Alors, de quoi avez-vous besoin ?

Au moment de la rédaction de cet article, Apple teste toujours en version bêta le nouveau macOS Catalina, XCode 11, iOS 13 et quelques autres produits qui devraient bientôt arriver.

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.

screen-shot-2019-08-26-at-09-07-25

Pas Catalina ? Pas d’aperçu !

C’est une décision que vous devez prendre car j’ai découvert que le passage de Bash à ZSH comme shell officiel de MacOS à Catalina cassait beaucoup de mes outils et scripts que j’utilise pour mon travail quotidien, j’ai décidé de rester sur Mojave pour le moment. C’est à vous de décider si vous allez mettre à jour ou non.

De plus, vous aurez besoin d’un compte Back4app avec une application créée. Vous pouvez apprendre comment créer votre première application avec Back4app ici.

Enfin, nous utiliserons le client Apollo afin d’intégrer GraphQL à notre projet. Si vous n’avez pas d’expérience dans ce domaine, j’ai écrit un article très détaillé sur la façon de le configurer et de l’utiliser ici.

Donc, avant même de commencer, vous aurez besoin :

  • XCode 11 installé (avec macOS Catalina si vous voulez avoir les Aperçus)
  • Votre nouvelle application créée dans Back4app
  • Client Apollo 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, afin que vous puissiez inscrire de nouveaux utilisateurs et authentifier les utilisateurs existants, et la classe Role, afin que vous puissiez regrouper des utilisateurs avec des privilèges similaires. C’est formidable car, au moins pour l’authentification, nous n’avons pas à créer de classe.

La classe User comporte trois propriétés créées automatiquement, dont deux obligatoires et une facultative :

  • Nom d’utilisateur (obligatoire)
  • Mot de passe (obligatoire)
  • Email (facultatif)

Ces informations sont suffisantes pour que notre application puisse enregistrer et gérer les utilisateurs.

Comme vous le savez probablement, la politique d’Apple en matière de stockage des emails des utilisateurs est très stricte, je vous recommande donc de ne pas demander ou conserver ces informations, sous peine de ne pas être approuvé.

Comme il s’agit uniquement d’un tutoriel, je vais demander à l’utilisateur de saisir ces informations, juste pour que vous puissiez voir comment cela fonctionne.

screen-shot-2019-08-26-at-09-30-18

Création automatique de classes User et Role

Comme si cela ne suffisait pas, votre application nouvellement créée contient déjà des requêtes et mutations GraphQL créées automatiquement pour nous. Ceci est vrai pour nos classes créées automatiquement et aussi pour toute autre classe que vous créez au fil du temps. Nous vous aidons aussi avec la documentation !

screen-shot-2019-08-26-at-09-39-46

Vous ai-je dit que nous avons aussi l’Autocomplet ? Nous l’avons !

Donc, ma première mutation sera comme ci-dessus, et utilisera notre signature de mutation spécifique() 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) {
  users {
    signUp(
      fields: { 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 nom d’utilisateur et mot de passe obligatoires en mettant un  » !  » à la fin, alors que l’email est facultatif, je ne mets pas le  » ! « .

En fin de compte, je renvoi l’objectId de l’utilisateur nouvellement créé.

Et puisque nous sommes ici, 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 pour l’utilisateur :

Parse 3.7.2

mutation logInUser($username: String!, $password: String!){
  users{
    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 ne renvoie qu’un booléen :

Parse 3.7.2

mutation logOutUser{
  users{
    logOut
  }
}

Parse 3.8

mutation logOutUser{
    logOut
}

Parse 3.9

mutation logOutUser{
    logOut
}

Ajoutez tout cela dans un nouveau fichier appelé UserMutations.graphql, et 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 « Créer un nouveau projet XCode » dans la fenêtre « Bienvenue à XCode ».

Si vous n’obtenez pas cette fenêtre, vous pouvez toujours aller dans Fichier > Nouveau > Projet.

Choisissez l’application iOS en haut, puis l’application à vue unique. Donnez-lui un bon nom et n’oubliez pas de choisir Swift comme langue et SwiftUI comme interface utilisateur :

screen-shot-2019-08-26-at-10-15-21

N’oubliez pas de sélectionner SwiftUI comme interface utilisateur

Maintenant, installez le client Apollo comme vous l’avez appris dans notre article, et ensuite, ajoutez les fichiers UserMutations.graphql et schema.graphql à votre projet. Compiler et ajouter le fichier API.swift nouvellement généré au projet.

À ce stade, ce que j’attends de vous est :

  • XCode 11 installé et en cours d’utilisation
  • Client Apollo installé et intégré dans votre projet XCode
  • Fichiers UserMutations.graphql et schema.graphql ajoutés à votre projet XCode
  • Fichier API.swift généré ajouté à votre projet XCode

Votre projet devrait ressembler à celui-ci :

screen-shot-2019-08-26-at-10-32-00

Tout est intégré dans le XCode 11

Nous sommes maintenant prêts à lancer quelques SwiftUI’ing. Inscrivez-vous maintenant à Back4App et commencez à construire votre application Clone Instagram.

Décider de nos contrôles

Avant de pouvoir se connecter ou se déconnecter d’un utilisateur, nous devons d’abord créer cet utilisateur. Nous allons donc d’abord coder notre écran d’inscription.

Habituellement, 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 la saisie de texte)
  • Mot de passe (contrôle de la saisie sécurisée du texte)
  • Confirmer le mot de passe (facultatif, dépend de la plate-forme, contrôle de la saisie sécurisée du texte)
  • Courriel (contrôle de la saisie de texte formaté)
  • Bouton d’inscription (bouton)

Comme il s’agira d’une application mobile, j’ai décidé de ne pas mettre le contrôle de la saisie du texte de confirmation du mot de passe, 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 est tapé correctement.

Ces contrôles seront affichés alignés verticalement, l’un en dessous de l’autre, dans l’ordre ci-dessus, chacun avec sa propre couleur.

Créer en fait l’interface utilisateur

Comme je l’ai déjà mentionné, je n’utilise pas macOS Catalina et je n’activerai donc pas les aperçus, mais je vais compiler et vous montrer les résultats en même temps que mon code tel que je l’ai.

Créez une nouvelle vue SwiftUI en allant dans Fichier > Nouveau > Fichier, puis en choisissant Vue SwiftUI dans la section Interface utilisateur.

screen-shot-2019-08-26-at-11-08-58

Nommez ce fichier SignUpView.swift et il apparaîtra intégré à votre projet.

screen-shot-2019-08-26-at-11-14-13

Vous remarquerez que la vue nouvellement créée a déjà un contrôle Texte avec le « Hello World! » string dedans.

screen-shot-2019-08-26-at-11-18-31

La beauté de Swift est que vous pouvez créer plusieurs vues simples, ne contenant que les commandes 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 (pile verticale) : 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, ajoutons deux fois le SignUpView :

screen-shot-2019-08-26-at-11-39-39

Ajout de SignUpView deux fois dans le VStack : il s’affiche deux fois en alignement vertical

C’est vraiment cool ! Si nous avons besoin de plus de contrôles, nous pouvons simplement continuer à les ajouter !

Supprimons VStack et la duplication et n’avons qu’une seule SignUpView() dans cette vue, puis retournons à notre SignUpView et commençons à la modifier, car nous avons déjà vu les résultats de notre test. Votre ContentView devrait ressembler à cela maintenant :

struct ContentView: View {
    var body: some View {
        SignUpView()
    }
}

Nous savons déjà que nous aurons besoin de ce VStack avec du texte en haut de l’écran, alors ajoutons simplement le VStack à la SignUpView à la place 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 comme nous savons déjà quels contrôles nous devons ajouter en dessous, nous pouvons en ajouter :

  • Un TextField pour le nom d’utilisateur
  • Un SecureField pour le mot de passe
  • Un TextField pour l’email
  • Un bouton d’inscription

Les TextFields et SecureField sont tous deux assez similaires à l’ancien UITextField, mais ils doivent s’appuyer sur l’obligation d’indiquer, c’est pourquoi nous allons les déclarer comme tels :

Control(« Placeholder », text: stateValueToBindTo)

Et la toute première étape consiste à déclarer ces États comme étant liés :

@State var username: String = ""
@State var password: String = ""
@State var email: String = ""

Cela étant fait, nous pouvons commencer à ajouter nos contrôles :

Text("Sign Up")
TextField("Username", text: $username)
SecureField("Password", text: $password)
TextField("Email (optional)", text: $email)

Enfin, nous pouvons ajouter notre Bouton, qui a cette structure :

Button(action: {
//Code to run when triggered
}){
//Content
}

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 lorsque l’on appuie sur la touche.

Notre bouton comportera un texte disant « Sign Up », alors ajoutons-le :

Button(action: {

}){
    Text("Sign Up!")
}

Et 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("Sign Up")
            TextField("Username", text: $username)
            SecureField("Password", text: $password)
            TextField("Email (optional)", text: $email)
            Button(action: {
                
            }){
                Text("Sign Up!")
            }
        }
    }
}

Et qu’en est-il de notre aperçu ?

screen-shot-2019-08-26-at-16-11-48

Cela semble prometteur, mais nous pouvons améliorer beaucoup de choses en définissant quelques propriétés visuelles.

Et s’il y a quelque chose de super sympa avec SwiftUI, c’est que vous pouvez ajouter des propriétés visuelles au code et voir les changements en temps réel aussi !

Essayons-en quelques-uns !

Tout d’abord, déclarons les couleurs que nous utiliserons

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)

Ensuite ajoutons quelques fonctionnalités intéressantes :

Un peu de Padding pour que nous ayons un peu d’espace entre ces éléments.

Définissez les propriétés de couleur d’arrière-plan et de premier plan afin d’avoir des contrôles cohérents avec notre marque.

Un peu de cornerRadius pour que nous puissions avoir des coins arrondis.
Le réglage de la police et de sa taille pour rendre les choses plus agréables.

Voilà !

screen-shot-2019-08-26-at-16-30-55

C’est mieux ou pas ? Juste pour que vous gardiez une trace de 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("Sign Up")
                .font(.largeTitle)
                .foregroundColor(lightBlueColor)
                .fontWeight(.semibold)
                .padding(.bottom, 20)
            TextField("Username", text: $username)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            SecureField("Password", text: $password)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            TextField("Email (optional)", text: $email)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            Button(action: {
                
            }){
                Text("Sign Up!")
                 .font(.headline)
                 .foregroundColor(.white)
                 .padding()
                 .frame(width: 220, height: 60)
                 .background(lightBlueColor)
                 .cornerRadius(15.0)
            }
        }.padding()
    }
}

Donc, notre bel écran est presque prêt. A propos de…

Certaines fonctionnalités

Maintenant que notre interface utilisateur 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 devrions créer un client Apollo dans un endroit où toutes les vues peuvent y accéder. Cet endroit est l’AppDelegate.swift.

Déclarez votre client Apollo dans le UIKit d’importation ci-dessous.

import UIKit

Et le début du bloc de code.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...

Comme nous vous l’avons montré dans notre article précédent, et désormais toutes les vues peuvent 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

// Apollo Client initialization.
// More about it here: 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 l’inscription lorsque nous cliquons sur le bouton Inscription, donc, dans le bloc d’action, appelons-le :

            // Perform the SignUpUser mutation, passing the parameters we just got from our TextFields
            apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in
                // Let's switch the result so we can separate a successful one from an error
                switch result {
                    // In case of success
                    case .success(let graphQLResult):
                        // We try to parse our result
                        if let objId = graphQLResult.data?.users?.signUp.objectId {
                            print ("User created with ObjectId: " + objId)
                        }
                        // but in case of any GraphQL errors we present that message
                        else if let errors = graphQLResult.errors {
                            // GraphQL errors
                            print(errors)
                            
                        }
                    // In case of failure, we present that message
                    case .failure(let error):
                      // Network or response format errors
                      print(error)
                }
            }

Cela fonctionnera, mais ces méthodes print() n’imprimeront que sur la console. Nous devons présenter une Alerte à notre utilisateur, alors changeons pour une Alerte, avec la structure suivante :

Alert(title: Text(« Title »), message: Text(« Message »), dismissButton: .default(Text(« TextOfButton »)))

Comme nous devrons changer le titre et le message en cours d’exécution, nous devons définir une structure pour gérer ces valeurs, car les variables propres ne peuvent pas être modifiées en cours d’exécution :

struct Message {
    var alertTitle: String = ""
    var alertText: String = ""
}

var myMessage = Message()

Et aussi créer un État afin que la Vue puisse savoir quand afficher l’alerte :

@State private var showingAlert = false

Et notre code complet pour notre action Bouton devrait être comme ceci :

Button(action: {
            // Perform the SignUpUser mutation, passing the parameters we just got from our TextFields
            apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in
                // Let's switch the result so we can separate a successful one from an error
                switch result {
                    // In case of success
                    case .success(let graphQLResult):
                        // We try to parse our result
                        if let objId = graphQLResult.data?.users?.signUp.objectId {
                            myMessage.alertTitle = "Yay!"
                            myMessage.alertText = "User signed up!"
                            
                            self.showingAlert = true

                            print ("User created with ObjectId: " + objId)
                        }
                        // but in case of any GraphQL errors we present that message
                        else if let errors = graphQLResult.errors {
                            // GraphQL errors

                            myMessage.alertTitle = "Oops!"
                            myMessage.alertText = "We've got a GraphQL error: " + errors.description
                            self.showingAlert = true

                            print(errors)
                        }
                    // In case of failure, we present that message
                    case .failure(let error):
                        // Network or response format errors
                        myMessage.alertTitle = "Oops!"
                        myMessage.alertText = "We've got an error: " + error.localizedDescription
                        self.showingAlert = true
                        
                        print(error)
                }
            }
           }){
               Text("Sign Up!")
                .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, n’est-ce pas ?

Voici le moment tant attendu

Compiler et exécuter. Essayez d’inscrire un nouvel utilisateur et vous devriez obtenir…

screen-shot-2019-08-27-at-15-40-59

Voyons si l’utilisateur est créé dans notre tableau de bord Parse !

screen-shot-2019-08-27-at-15-42-30

Conclusion

Vous avez réalisé une vue SwiftUI entièrement fonctionnelle qui effectue des mutations GraphQL à l’aide du client Apollo. C’est génial !

Nous allons continuer à travailler sur notre clone d’Instagram ! Les prochaines étapes sont la connexion et la déconnexion, l’article est déjà en route !

Restez à l’écoute !

Référence :

Inscrivez-vous maintenant à Back4App et commencez à construire votre application Clone Instagram.

Qu’est-ce qu’un clone d’Instagram ?

Il s’agit d’une application de réseau social qui ressemble et présente des caractéristiques similaires à Instagram. La structure backend est disponible sur iOS Instagram Clone.

Quelles sont les exigences pour développer un clone d’Instagram ?

Les exigences sont XCode 11 avec macOS Mojave, macOS Catalina et le client Appolo.

Où puis-je télécharger un clone d’Instagram ?

Voici le lien pour télécharger le clone de l’Instagram iOS.


Leave a reply

Your email address will not be published.