Un clone d’Instagram utilisant SwiftUI et GraphQL – Connexion

Dans notre précédent article sur la création d’une application clone d’Instagram, vous avez appris à tout configurer pour que SwiftUI soit opérationnel dans XCode 11, et vous avez créé une vue Sing Up qui fonctionne parfaitement avec GraphQL.

Aujourd’hui, nous allons apprendre à créer une vue de connexion et à faire en sorte que l’utilisateur se déconnecte.

Nous aurons besoin du projet de l’article précédent, donc si vous ne l’avez pas suivi, je vous suggère fortement de le faire.

Attachez vos ceintures et c’est parti !

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

Vous voulez commencer rapidement ?

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

Création de la vue de connexion

Notre vue Login sera assez similaire à notre vue SignUp, même plus simple en fait.

Dans la mutation logInUser, nous n’avons besoin que de deux paramètres : le nom d’utilisateur et le mot de passe :

Les requêtes et les mutations dépendent de la version de Parse que vous avez choisie :

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
    }
}

nous n’aurons donc qu’à demander cela à nos utilisateurs.

Commençons par ajouter une nouvelle vue SwiftUI en allant dans Fichier > Nouveau > Fichier et en sélectionnant notre vue SwiftUI.

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

Nommons cette vue LogInView.swift et ajoutons-la à notre projet :

screen-shot-2019-08-28-at-10-54-07

Et comme vous l’avez déjà appris, créez votre VStack avec les contrôles dont nous aurons besoin :

  • TextField pour le nom d’utilisateur
  • SecureField pour le mot de passe
  • Bouton pour effectuer l’action

Pour garder un design cohérent, j’ai déplacé les couleurs que nous utiliserons dans AppDelegate.swift, j’ai donc dû importer SwiftUI là aussi :

import SwiftUI

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)

N’oubliez pas de supprimer les lignes de couleur dans SignUpView.swift et LogInView.swift.

De plus, afin de conserver la cohérence des contrôles, j’ai simplement fait un copier-coller de notre vue SignUp et j’ai supprimé le champ TextField de l’email et modifié les champs TextFields pour refléter les nouvelles fonctionnalités. Mon code a fini par ressembler à ceci :

struct LogInView : View {
    @State var username : String = ""
    @State var password : String = ""
    
    @State private var showingAlert = false

    var body : some View {
       VStack{
           Text("Log In")
               .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)
           Button(action : {
            
           }){
               Text("Se connecter !")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(width : 220, height : 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
       }.padding()
    }
}

C’était simple. Voyons ce que cela donne ?
Modifiez votre ContentView.swift pour afficher cette vue à la place :

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

screen-shot-2019-08-28-at-11-16-13

Ça a l’air sympa !

Rendons-le encore plus soigné en ajoutant notre logo en haut !
Notre logo se compose d’une image que nous intégrerons à notre projet. J’ai utilisé celle-ci.

Glissez et déposez cette image dans votre dossier Assets.xcassets dans le projet :

screen-shot-2019-08-28-at-13-25-54

Il devrait ressembler à ceci :

screen-shot-2019-08-28-at-13-27-30

A partir de maintenant, nous pouvons la référencer dans notre code avec ce nom : logo-social.

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

Notre logo sera composé de cette image, mais le simple fait de mettre l’image aurait l’air d’un amateur. Nous allons le faire briller : circulaire, avec une taille fixe, quelques traits sur les bords et, bien sûr, une ombre portée à cause de… l’ombre portée.

Le code pour tout cela ressemble à ceci :

Image("logo-social")
    .resizable()
    .aspectRatio(contentMode : .fit)
    .frame(width : 150, height : 150)
    .clipShape(Circle()))
    .overlay(Circle().stroke(Color.blue, lineWidth : 2))
    .shadow(radius : 5)
    .padding(.bottom, 75)

Et il est placé au sommet de notre VStack :

var body : some View {
       VStack{
           Image("logo-social")
                .resizable()
                .aspectRatio(contentMode : .fit)
                .frame(width : 150, height : 150)
                .clipShape(Circle()))
                .overlay(Circle().stroke(Color.blue, lineWidth : 2))
                .shadow(radius : 5)
                .padding(.bottom, 75)
           Texte("Se connecter")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
...

screen-shot-2019-08-28-at-13-33-41

N’est-ce pas magnifique ?

Le sessionToken

Notre processus de connexion renverra une chaîne sessionToken. Nous devons garder ce sessionToken en sécurité car il sera utilisé lors de nos opérations : tant que ce sessionToken est valide, nous aurons accès à notre application. S’il est supprimé ou invalidé, nos appels seront refusés.

Comme il s’agit d’un élément directement lié à la sécurité, nous devrons le stocker de manière sécurisée. Le bon endroit pour le faire est le trousseau sur iOS.

L’inconvénient du trousseau est qu’il est difficile et ennuyeux à utiliser, c’est pourquoi j’ai décidé d’utiliser ce wrapper pour le gérer. Cela rend la vie tellement plus facile et puisque nous utilisons déjà Cocoapods, c’est tout à fait logique.

Modifions notre Podfile et ajoutons ceci à nos pods :

pod 'SwiftKeychainWrapper'

puis mettons à jour nos Pods avec la commande

pod install

et enfin, rouvrons notre projet xcworkspace.

Nous sommes maintenant prêts à utiliser notre moteur Keychain pour…

Stocker le sessionToken

D’après la documentation de notre nouveau pod, la façon de sauvegarder une valeur dans le trousseau est la suivante

KeychainWrapper.standard.set("SomeValue", forKey : "SomeKey")

mais ajoutons-y un peu de logique.

Pour commencer, importons notre wrapper :

import SwiftKeychainWrapper

La première chose à faire est d’appeler notre mutation logInUser et lorsque celle-ci répond, nous stockons le sessionToken s’il y en a un. Si ce n’est pas le cas, nous devons avertir l’utilisateur avec une alerte.

Si vous vous souvenez de notre article précédent, nous avons déjà codé une alerte, y compris sa structure. Réutilisons-la en supprimant le code ci-dessous de notre SingUpView.swift et en le passant à notre fichier AppDelegate.swift afin que toutes les vues puissent y accéder :

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

var myMessage = Message()

Pour en revenir à notre logique, la première chose à faire est de déterminer si l’utilisateur a rempli les champs de texte du nom d’utilisateur et du mot de passe. Si ce n’est pas le cas, il n’y a pas d’informations pour le processus de connexion et nous devons en informer l’utilisateur.

Notre code d’action pour le bouton de connexion doit le vérifier. Ajoutons ce morceau de code qui vérifie la taille de la chaîne des variables d’état liées à ces champs de texte :

           Button(action : {
            // Vérifie si un mot de passe a été saisi
            if (self.password.count == 0 || self.username.count == 0){
                
                // Si ce n'est pas le cas, nous devons afficher l'alerte
                myMessage.alertText = "Vous devez fournir un nom d'utilisateur et un mot de passe."
                myMessage.alertTitle = "Oups..."
                self.showingAlert = true
            } else {
                // Si c'est le cas, nous pouvons continuer
                
            }
           }){
               Text("Log In !")
                .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")))
           }

et le tester…

screen-shot-2019-09-02-at-10-22-15

C’est très bien !

Nous devons maintenant appeler notre mutation GraphQL afin de récupérer le sessionToken et, si nous en obtenons un, de le stocker dans le trousseau.
Vous avez déjà appris comment appeler les mutations, alors faisons-le, mais cette fois-ci, si nous obtenons le sessionToken, nous le stockerons :

// Effectuer la mutation LogInUser, en passant les paramètres que nous venons d'obtenir de nos TextFields
apollo.perform(mutation : LogInUserMutation(username : self.username, password : self.password)){ 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 sessionToken = graphQLResult.data ?.users ?.logIn.sessionToken {
                myMessage.alertTitle = "Yay !"
                myMessage.alertText = "L'utilisateur s'est connecté !"
                
                self.showingAlert = true

                print ("User sessionToken " + sessionToken)
                

                // Ecrire le sessionToken dans notre Keychain
                let _ : Bool = KeychainWrapper.standard.set(sessionToken, forKey : "Back4Gram.sessionToken")
            }
            // mais en cas d'erreur GraphQL, nous présentons 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)
    }
}

Testons-le !

screen-shot-2019-09-02-at-13-11-02

C’est chouette ! Mais comment savoir si cela a fonctionné ?
Nous pouvons aller sur le tableau de bord Parse de notre application et vérifier si un nouvel objet Session a été écrit :

screen-shot-2019-09-02-at-13-11-34

Comme un charme !

Et puisque nous en sommes là…

Pourquoi ne pas ajouter un bouton pour se déconnecter ? Juste pour tester, afin de s’assurer que tout se passe bien :

Button(action : {
    // Vérifier s'il y a un sessionToken storeString ne devrait se déconnecter que si l'on est connecté.
    if (KeychainWrapper.standard.string(forKey : "Back4Gram.sessionToken") != nil) {
        print("Found sessionToken ! We can logout.")
        
        // Effectuer la mutation LogOutUser
        apollo.perform(mutation : LogOutUserMutation()){ 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 result = graphQLResult.data ?.users ?.logOut {
                        if (result) {
                            myMessage.alertTitle = "Yay !"
                            myMessage.alertText = "Utilisateur déconnecté !"
                            
                            self.showingAlert = true
                            
                            // Effacer le sessionToken stocké
                            let _ : Bool = KeychainWrapper.standard.set("", forKey : "Back4Gram.sessionToken")
                        } else {
                            myMessage.alertTitle = "Oops !"
                            myMessage.alertText = "L'opération de déconnexion de l'utilisateur a retourné False."
                            self.showingAlert = true
                        }
                    }
                    // 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)
            }
        }
    } else {
        // Erreurs de réseau ou de format de réponse
        myMessage.alertTitle = "Oops !"
        myMessage.alertText = "L'utilisateur ne semble pas être connecté."
        self.showingAlert = true
    }
}){
    Text("Déconnexion")
        .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")))
}

Encore une fois, testons-le !

screen-shot-2019-09-02-at-13-51-50

C’est chouette !

Nous déplacerons ce bouton de déconnexion ailleurs plus tard, mais pour l’instant, il permet de montrer que notre flux fonctionne.

Mais qu’en est-il de notre objet Session maintenant que nous nous sommes déconnectés ?

screen-shot-2019-09-02-at-14-04-31

Il a disparu automatiquement, comme prévu !

Conclusion

Nous vous félicitons ! Vous avez maintenant implémenté les fonctionnalités de connexion et de déconnexion ! En plus de cela, vous avez appris à appeler différentes mutations, à valider les résultats et à stocker les valeurs dans le trousseau ! C’est vraiment génial !

Dans le prochain chapitre, nous commencerons à travailler avec des vues multiples et à construire notre vue principale !

Restez à l’écoute !

Référence

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

Qu’est-ce que SwiftUI ?

SwiftUI est une nouvelle façon de créer des interfaces utilisateur pour les applications sur les plateformes Apple. Il permet aux développeurs de déterminer l’interface utilisateur à l’aide du code Swift.

Qu’est-ce qu’un sessionToken ?

Le processus de connexion que nous développons renverra une chaîne de sessionToken. Elle doit être sécurisée. Si nous parvenons à maintenir la validité de ce sessionToken, nous aurons accès à l’application ; sinon, nous perdrons l’accès à l’application. Ceci est lié à la sécurité.

Qu’est-ce que le trousseau ?

Nous savons que le sessionToken est lié à la sécurité de votre application. Il doit donc être stocké dans un endroit sûr, appelé trousseau. Son utilisation peut être un peu complexe et fastidieuse.


Leave a reply

Your email address will not be published.