Un clone di Instagram che utilizza SwiftUI e GraphQL – Login

Nel nostro precedente post su come creare un’app clone di Instagram, abbiamo imparato a configurare tutto per avere SwiftUI funzionante in XCode 11 e abbiamo creato una vista Sing Up perfettamente funzionante con GraphQL.

Oggi impareremo a creare una vista di login e a far uscire l’utente.

Avremo bisogno del progetto del post precedente, quindi se non lo avete seguito, vi consiglio vivamente di farlo.

Allacciate le cinture di sicurezza e partiamo!

Per imparare meglio, scaricate il progetto iOS Instagram Clone con il codice sorgente.

Volete un inizio rapido?

Clonate questa app dal nostro Hub e iniziate a usarla senza problemi!

Creare la vista di login

La nostra vista Login sarà molto simile alla vista SignUp, anzi è ancora più semplice.

Nella mutazione logInUser abbiamo bisogno solo di due parametri: nome utente e password:

Le query e le mutazioni dipendono dalla versione di Parse scelta:

Parse 3.7.2:

mutazione logInUser($username: String!, $password: String!){
  utenti{
    logIn(username: $username, password: $password){
      sessionToken
    }
  }
}

Parse 3.8:

mutazione logInUser($username: String!, $password: String!){
    logIn(username: $username, password: $password){
      sessionToken
    }
}

Parse 3.9:

mutazione logInUser($username: String!, $password: String!){
    logIn(nomeutente: $nomeutente, password: $password){
      sessionToken
    }
}

quindi dovremo chiedere solo questi per i nostri utenti.

Iniziamo aggiungendo una nuova vista SwiftUI andando su File > New > File e selezionando la nostra vista SwiftUI.

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

Chiamiamo questa vista LogInView.swift e apriamola nel nostro progetto:

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

E come avete già imparato, create il vostro VStack con i controlli di cui avremo bisogno:

  • TextField per il nome utente
  • Campo sicuro per la password
  • Pulsante per eseguire l’azione

Per mantenere il design coerente, ho spostato i colori che utilizzeremo in AppDelegate.swift, quindi ho dovuto importare SwiftUI anche lì:

import SwiftUI

let lightGreyColor = Color(rosso: 239.0/255.0, verde: 243.0/255.0, blu: 244.0/255.0, opacità: 1.0)
let lightBlueColor = Colore(rosso: 36.0/255.0, verde: 158.0/255.0, blu: 235.0/255.0, opacità: 1.0)

Ricordarsi di rimuovere le linee di colore da SignUpView.swift e LogInView.swift.

Inoltre, per mantenere la coerenza dei controlli, ho semplicemente copiato e incollato dalla nostra vista SignUp, rimuovendo il campo di testo dell’e-mail e cambiando i campi di testo per riflettere le nuove funzionalità. Il mio codice è finito così:

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

    var body: some View {
       VStack{
           Testo("Accedi")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
           Campo di testo("Nome utente", testo: $nomeutente)
               .padding()
               .background(lightGreyColor)
               .cornerRadius(5.0)
               .padding(.bottom, 20)
           SecureField("Password", testo: $password)
               .padding()
               .background(lightGreyColor)
               .cornerRadius(5.0)
               .padding(.bottom, 20)
           Button(action: {
            
           }){
               Testo("Accedi!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(larghezza: 220, altezza: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
       }.padding()
    }
}

È stato semplice. Vediamo come appare?
Modificare il file ContentView.swift per mostrare questa vista:

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

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

Sembra pulito!

Rendiamolo più ordinato aggiungendo il nostro logo in alto!
Il nostro logo consisterà in un’immagine che integreremo nel nostro progetto. Io ho usato questa.

Trascinate l’immagine nella cartella Assets.xcassets del progetto:

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

Dovrebbe avere questo aspetto:

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

D’ora in poi potremo fare riferimento ad essa nel nostro codice con questo nome: logo-social.

Iscrivetevi ora a Back4App e iniziate a costruire la vostra app clone di Instagram.

Il nostro logo consisterà in questa immagine, ma metterla semplicemente lì avrebbe un aspetto amatoriale. Lo faremo brillare: circolare, con una dimensione fissa, qualche tratto sui bordi e, ovviamente, drop shadow perché… drop shadow.

Il codice per tutto ciò assomiglia a questo:

Immagine("logo-social")
    .ridimensionabile()
    .aspectRatio(contentMode: .fit)
    .frame(larghezza: 150, altezza: 150)
    .clipShape(Circle())
    .overlay(Circle().stroke(Color.blue, lineWidth: 2))
    .shadow(raggio: 5)
    .padding(.bottom, 75)

E va in cima al nostro VStack:

var body: some View {
       VStack{
           Immagine("logo-social")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(larghezza: 150, altezza: 150)
                .clipShape(Circle())
                .overlay(Circle().stroke(Color.blue, lineWidth: 2))
                .shadow(raggio: 5)
                .padding(.bottom, 75)
           Testo("Accedi")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
...

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

Non è bellissimo?

Il sessionToken

Il nostro processo di login restituirà una stringa sessionToken. Dobbiamo mantenere il sessionToken al sicuro, perché sarà usato durante le nostre operazioni: finché il sessionToken è valido, avremo accesso alla nostra applicazione. Quando viene cancellato o invalidato, le nostre chiamate saranno negate.

Poiché questo è direttamente collegato alla sicurezza, dovremo memorizzarlo in modo sicuro. Il posto giusto per farlo è il Portachiavi di iOS.

L’unico problema del Portachiavi è che è difficile e noioso da usare, quindi ho deciso di usare questo wrapper per gestirlo. Rende la vita molto più facile e, dato che stiamo già usando Cocoapods, ha perfettamente senso.

Modifichiamo il nostro Podfile e aggiungiamo questo ai nostri pod:

pod 'SwiftKeychainWrapper'

quindi aggiorniamo i nostri Pod con il comando

pod install

e infine riapriamo il nostro progetto xcworkspace.

Ora siamo pronti a usare il nostro motore Keychain per…

Memorizzare il sessionToken

Secondo la documentazione del nostro nuovo pod, il modo per salvare un valore nel portachiavi è

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

ma aggiungiamo anche un po’ di logica.

Per iniziare, importiamo il nostro wrapper:

importare SwiftKeychainWrapper

La prima cosa da fare è chiamare la mutazione logInUser e, quando questa risponde, memorizzare il sessionToken, se presente. In caso contrario, dobbiamo notificare all’utente un avviso.

Ora, se ricordate il nostro precedente articolo, abbiamo già codificato un avviso, compresa la sua struttura. Riutilizziamolo rimuovendo il codice sottostante dal nostro SingUpView.swift e passandolo al nostro file AppDelegate.swift, in modo che tutte le viste possano accedervi:

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

var myMessage = Messaggio()

Ora, tornando alla nostra logica, la prima cosa da fare è determinare se l’utente ha riempito le caselle di testo del nome utente e della password. In caso contrario, non ci sono informazioni per il processo di login e dobbiamo notificarlo all’utente.

Il nostro codice d’azione per il pulsante di login deve verificarlo. Aggiungiamo questo pezzo di codice che controlla la dimensione delle stringhe delle variabili di stato collegate ai campi di testo:

           Button(action: {
            // Controlla se è stata digitata una password
            if (self.password.count == 0 || self.username.count == 0){
                
                // In caso contrario, dobbiamo mostrare l'alert
                myMessage.alertText = "È necessario fornire un nome utente e una password".
                myMessage.alertTitle = "Oops..."
                self.showingAlert = true
            } else {
                // Se sì, possiamo procedere
                
            }
           }){
               Text("Accedi!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(larghezza: 220, altezza: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
           .alert(isPresented: $showingAlert) {
               Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText), dismissButton: .default(Text("OK")))
           }

e testarlo…

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

Ottimo!

Ora dobbiamo chiamare la nostra mutazione GraphQL per recuperare il sessionToken e, se lo otteniamo, memorizzarlo nel Portachiavi.
Abbiamo già imparato a chiamare le mutazioni, quindi facciamolo, ma questa volta se otteniamo il sessionToken, lo memorizziamo:

// Eseguiamo la mutazione LogInUser, passando i parametri appena ottenuti dai nostri campi di testo
apollo.perform(mutazione: LogInUserMutation(username: self.username, password: self.password)){ risultato in
    // Scambiamo il risultato in modo da poter separare un risultato di successo da un errore
    switch risultato {
        // In caso di successo
        case .success(let graphQLResult):
            // Cerchiamo di Parse il risultato
            if let sessionToken = graphQLResult.data?.users?.logIn.sessionToken {
                myMessage.alertTitle = "Evviva!"
                myMessage.alertText = "L'utente si è registrato!"
                
                self.showingAlert = true

                print ("Utente sessionToken " + sessionToken)
                

                // Scrivere il sessionToken nel nostro portachiavi
                let _: Bool = KeychainWrapper.standard.set(sessionToken, forKey: "Back4Gram.sessionToken")
            }
            // ma in caso di errori GraphQL presentiamo questo messaggio
            else if let errors = graphQLResult.errors {
                // Errori GraphQL

                myMessage.alertTitle = "Oops!"
                myMessage.alertText = "Abbiamo un errore GraphQL: " + errors.description
                self.showingAlert = true

                print(errori)
            }
        // In caso di fallimento, presentiamo questo messaggio
        case .failure(let error):
            // Errori di rete o di formato della risposta
            myMessage.alertTitle = "Oops!"
            myMessage.alertText = "Si è verificato un errore: " + error.localizedDescription
            self.showingAlert = true
            
            print(errore)
    }
}

Testiamolo!

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

Bello! Ma come facciamo a sapere che ha funzionato?
Possiamo andare nella Parse Dashboard della nostra App e verificare se è stato scritto un nuovo oggetto Session:

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

Come d’incanto!

E visto che siamo qui…

Che ne dite di aggiungere un pulsante per il logout? Solo per fare un test, in modo da sapere che tutto va bene:

Button(action: {
    // Controlla se c'è un sessionToken storeStringere il logout solo se si è connessi.
    if (KeychainWrapper.standard.string(forKey: "Back4Gram.sessionToken") != nil) {
        print("Trovato sessionToken! Possiamo effettuare il logout").
        
        // Eseguire la mutazione LogOutUser
        apollo.perform(mutazione: LogOutUserMutation()){ risultato in
            // Scambiamo il risultato in modo da poter separare un successo da un errore
            switch risultato {
                // In caso di successo
                case .success(let graphQLResult):
                    // Cerchiamo di Parse il nostro risultato
                    if let result = graphQLResult.data?.users?.logOut {
                        if (result) {
                            myMessage.alertTitle = "Yay!"
                            myMessage.alertText = "Utente disconnesso!"
                            
                            self.showingAlert = true
                            
                            // Cancellare il sessionToken memorizzato
                            let _: Bool = KeychainWrapper.standard.set("", forKey: "Back4Gram.sessionToken")
                        } else {
                            myMessage.alertTitle = "Oops!"
                            myMessage.alertText = "L'operazione di logout dell'utente ha dato esito negativo".
                            self.showingAlert = true
                        }
                    }
                    // ma in caso di errori GraphQL presentiamo quel messaggio
                    else if let errors = graphQLResult.errors {
                        // Errori GraphQL

                        myMessage.alertTitle = "Oops!"
                        myMessage.alertText = "Abbiamo un errore GraphQL: " + errors.description
                        self.showingAlert = true

                        print(errori)
                    }
                // In caso di fallimento, presentiamo questo messaggio
                case .failure(let error):
                    // Errori di rete o di formato della risposta
                    myMessage.alertTitle = "Oops!"
                    myMessage.alertText = "Si è verificato un errore: " + error.localizedDescription
                    self.showingAlert = true
                    
                    print(errore)
            }
        }
    } else {
        // Errori di rete o di formato della risposta
        myMessage.alertTitle = "Oops!"
        myMessage.alertText = "L'utente non sembra essere connesso".
        self.showingAlert = true
    }
}){
    Testo("Esci")
        .font(.headline)
        .foregroundColor(.white)
        .padding()
        .frame(larghezza: 220, altezza: 60)
        .background(lightBlueColor)
        .cornerRadius(15.0)
}
.alert(isPresented: $showingAlert) {
    Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText), dismissButton: .default(Text("OK")))
}

Ancora una volta, testiamolo!

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

Bello!

Sposteremo il pulsante di logout da qualche altra parte più avanti, ma per ora funziona per mostrare che il nostro flusso funziona.

Ma che ne è del nostro oggetto Session, ora che ci siamo disconnessi?

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

È automaticamente sparito, come previsto!

Conclusione

Congratulazioni! Ora avete implementato le funzionalità di login e logout! Non solo, ma avete imparato a chiamare diverse mutazioni, a convalidare i risultati e a memorizzare i valori nel Portachiavi! Che meraviglia!

Nel prossimo capitolo, inizieremo a lavorare con le viste multiple e a costruire la nostra vista principale!

Restate sintonizzati!

Riferimento

Registratevi ora a Back4App e iniziate a costruire la vostra app clone di Instagram.

Che cos’è SwiftUI?

SwiftUI è un nuovo modo per creare interfacce utente per le applicazioni sulle piattaforme Apple. Consente agli sviluppatori di determinare l’interfaccia utente utilizzando il codice Swift.

Che cosa è un sessionToken?

Il processo di login che stiamo sviluppando restituirà una stringa sessionToken. Deve essere protetto. Se riusciamo a mantenere valido il sessionToken avremo accesso all’applicazione, altrimenti lo perderemo. È una questione di sicurezza.

Che cosa è un portachiavi?

Sappiamo che sessionToken è correlato alla sicurezza della tua app. Quindi, deve essere conservato in un luogo sicuro. Questo luogo sicuro si chiama portachiavi. Può essere un po’ complicato da usare e può anche risultare noioso.


Leave a reply

Your email address will not be published.