Ein Instagram-Klon mit SwiftUI und GraphQL – Anmeldung

In unserem vorherigen Beitrag über die Erstellung einer Instagram-Klon-App haben Sie gelernt, wie Sie alles konfigurieren, um SwiftUI in XCode 11 zum Laufen zu bringen, und eine voll funktionsfähige Sing Up-Ansicht mit GraphQL erstellt.

Heute werden wir lernen, wie man eine Login-Ansicht erstellt und den Benutzer abmeldet.

Dazu benötigen wir das Projekt aus dem vorherigen Beitrag. Wenn Sie also diesen Beitrag nicht verfolgt haben, empfehle ich Ihnen dringend, dies zu tun.

Schnallen Sie sich an und los geht’s!

Um besser zu lernen, können Sie das iOS Instagram Clone Projekt mit Quellcode herunterladen.

Wollen Sie einen schnellen Start?

Klonen Sie diese App von unserem Hub und fangen Sie an, sie ohne Probleme zu benutzen!

Erstellen der Login-Ansicht

Unsere Login-Ansicht wird unserer SignUp-Ansicht sehr ähnlich sein, eigentlich sogar noch einfacher.

In der logInUser Mutation benötigen wir nur zwei Parameter: Benutzername und Passwort:

Die Abfragen und Mutationen hängen von der Parse-Version ab, die Sie gewählt haben:

Parse 3.7.2:

mutation logInUser($username: String!, $password: String!){
  Benutzer{
    logIn(benutzername: $benutzername, passwort: $passwort){
      sessionToken
    }
  }
}

Parse 3.8:

Mutation logInUser($Benutzername: String!, $Passwort: String!){
    logIn(benutzername: $benutzername, passwort: $passwort){
      sessionToken
    }
}

Parse 3.9:

Mutation logInUser($Benutzername: String!, $Passwort: String!){
    logIn(benutzername: $benutzername, passwort: $passwort){
      sessionToken
    }
}

so dass wir nur diese für unsere Benutzer abfragen müssen.

Fügen wir zunächst eine neue SwiftUI-Ansicht hinzu, indem wir auf Datei > Neu > Datei gehen und unsere SwiftUI-Ansicht auswählen

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

Wir nennen diese Ansicht LogInView.swift und öffnen sie in unserem Projekt:

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

Und wie Sie bereits gelernt haben, erstellen Sie Ihren VStack mit den Steuerelementen, die wir benötigen:

  • TextField für den Benutzernamen
  • SecureField für das Passwort
  • Schaltfläche zum Ausführen der Aktion

Um das Design konsistent zu halten, habe ich die Farben, die wir verwenden werden, in die AppDelegate.swift verschoben, also musste ich auch dort SwiftUI importieren:

import SwiftUI

let lightGreyColor = Farbe(rot: 239.0/255.0, grün: 243.0/255.0, blau: 244.0/255.0, Deckkraft: 1.0)
let lightBlueColor = Farbe(rot: 36,0/255,0, grün: 158,0/255,0, blau: 235,0/255,0, Deckkraft: 1,0)

Denken Sie daran, die Farblinien aus SignUpView.swift und LogInView.swift zu entfernen.

Um die Konsistenz der Steuerelemente beizubehalten, habe ich einfach den Code aus der SignUp-Ansicht kopiert und eingefügt, das E-Mail-Textfeld entfernt und die Textfelder so geändert, dass sie die neuen Funktionalitäten widerspiegeln. Mein Code sah am Ende wie folgt aus:

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

    var body: some Ansicht {
       VStack{
           Text("Anmelden")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
           TextFeld("Benutzername", text: $Benutzername)
               .padding()
               .background(hellGrauFarbe)
               .cornerRadius(5.0)
               .padding(.bottom, 20)
           SecureField("Kennwort", text: $kennwort)
               .padding()
               .background(hellGrauFarbe)
               .cornerRadius(5.0)
               .padding(.bottom, 20)
           Button(action: {
            
           }){
               Text("Anmelden!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(Breite: 220, Höhe: 60)
                .background(hellblauFarbe)
                .cornerRadius(15.0)
           }
       }.padding()
    }
}

Das war einfach. Mal sehen, wie es aussieht?
Ändern Sie Ihre ContentView.swift, um stattdessen diese Ansicht anzuzeigen:

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

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

Sieht gut aus!

Fügen wir oben unser Logo ein, um es noch ordentlicher zu machen!
Unser Logo wird aus einem Bild bestehen, das wir in unser Projekt integrieren. Ich habe dieses Bild verwendet.

Ziehen Sie das Bild in den Ordner Assets.xcassets in Ihrem Projekt und legen Sie es dort ab:

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

Es sollte so aussehen:

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

Von nun an können wir es in unserem Code mit diesem Namen referenzieren: logo-social.

Melden Sie sich jetzt bei Back4App an und beginnen Sie mit der Erstellung Ihrer Instagram-Klon-App.

Unser Logo wird aus diesem Bild bestehen, aber einfach nur das Bild dorthin zu setzen, würde amateurhaft aussehen. Wir werden es zum Glänzen bringen: kreisförmig, mit einer festen Größe, ein paar Strichen an den Rändern und natürlich einem Schlagschatten wegen… Schlagschatten.

Der Code für all das sieht so aus:

Image("logo-social")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .frame(Breite: 150, Höhe: 150)
    .clipShape(Kreis())
    .overlay(Kreis().stroke(Farbe.blau, lineWidth: 2))
    .shadow(Radius: 5)
    .padding(.bottom, 75)

Und das Ganze kommt oben auf unseren VStack:

var body: some View {
       VStack{
           Image("logo-social")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(Breite: 150, Höhe: 150)
                .clipShape(Kreis())
                .overlay(Kreis().stroke(Farbe.blau, lineWidth: 2))
                .shadow(Radius: 5)
                .padding(.bottom, 75)
           Text("Anmelden")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
...

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

Na, ist das nicht schön?

Der SessionToken

Unser Anmeldevorgang gibt einen sessionToken-String zurück. Wir müssen diesen sessionToken sicher aufbewahren, da er während unserer Operationen verwendet wird: Solange der sessionToken gültig ist, haben wir Zugang zu unserer Anwendung. Wenn er gelöscht oder ungültig wird, werden unsere Aufrufe verweigert.

Da dies direkt mit der Sicherheit zusammenhängt, müssen wir ihn sicher speichern. Der richtige Ort dafür ist der Schlüsselbund unter iOS.

Das Problem mit dem Schlüsselbund ist, dass er schwierig und langweilig zu bedienen ist, daher habe ich mich entschieden, diesen Wrapper zu verwenden. Es macht das Leben so viel einfacher und da wir bereits Cocoapods verwenden, macht es absolut Sinn.

Bearbeiten wir unser Podfile und fügen wir dies zu unseren Pods hinzu:

pod 'SwiftKeychainWrapper'

Dann aktualisieren wir unsere Pods mit dem Befehl

pod installieren

und schließlich öffnen wir unser xcworkspace-Projekt erneut.

Wir sind nun bereit, unsere Keychain-Engine zu verwenden, um…

den SessionToken zu speichern

Laut der Dokumentation unseres neuen Pods wird ein Wert wie folgt im Schlüsselbund gespeichert

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

aber fügen wir noch etwas Logik hinzu.

Beginnen wir mit dem Import unseres Wrappers:

import SwiftKeychainWrapper

Als erstes müssen wir unsere logInUser-Mutation aufrufen, und wenn diese antwortet, speichern wir den sessionToken, falls es einen gibt. Wenn nicht, müssen wir den Benutzer mit einer Warnung benachrichtigen.

Wenn Sie sich an unseren vorherigen Artikel erinnern, haben wir bereits eine Benachrichtigung einschließlich ihrer Struktur kodiert. Lassen Sie uns das wiederverwenden, indem wir den untenstehenden Code aus unserer SingUpView.swift entfernen und ihn an unsere AppDelegate.swift-Datei übergeben, damit alle Ansichten darauf zugreifen können:

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

var myMessage = Nachricht()

Zurück zu unserer Logik: Als erstes müssen wir feststellen, ob der Benutzer die Textfelder für den Benutzernamen und das Passwort ausgefüllt hat. Wenn nicht, gibt es keine Informationen für den Anmeldevorgang und wir müssen den Benutzer darüber informieren.

Unser Aktionscode für die Login-Schaltfläche sollte dies überprüfen. Fügen wir dieses Codestück ein, das die Stringgröße der mit diesen Textfeldern verknüpften Statusvariablen überprüft:

           Button(action: {
            // Prüfen, ob ein Passwort eingegeben wurde
            if (self.password.count == 0 || self.username.count == 0){
                
                // Wenn nicht, müssen wir die Warnung anzeigen
                myMessage.alertText = "Sie müssen einen Benutzernamen und ein Passwort angeben."
                myMessage.alertTitle = "Oops..."
                self.showingAlert = true
            } else {
                // Wenn ja, können wir fortfahren
                
            }
           }){
               Text("Anmelden!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(Breite: 220, Höhe: 60)
                .background(hellblauFarbe)
                .cornerRadius(15.0)
           }
           .alert(isPresented: $showingAlert) {
               Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText)), dismissButton: .default(Text("OK")))
           }

und testen Sie es…

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

Schön!

Jetzt müssen wir unsere GraphQL-Mutation aufrufen, um den SessionToken abzurufen und, falls wir einen erhalten, im Schlüsselbund zu speichern.
Sie haben bereits gelernt, wie man Mutationen aufruft, also lassen Sie es uns tun, aber dieses Mal, wenn wir den sessionToken erhalten, werden wir ihn speichern:

// Führen Sie die LogInUser-Mutation durch und übergeben Sie die Parameter, die wir gerade von unseren TextFields erhalten haben
apollo.perform(mutation: LogInUserMutation(username: self.username, password: self.password)){ result in
    // Schalten wir das Ergebnis um, damit wir ein erfolgreiches Ergebnis von einem Fehler unterscheiden können
    switch ergebnis {
        // Im Falle eines Erfolgs
        case .success(let graphQLResult):
            // Wir versuchen, unser Ergebnis zu parsen
            if let sessionToken = graphQLResult.data?.users?.logIn.sessionToken {
                myMessage.alertTitle = "Juhu!"
                myMessage.alertText = "Benutzer eingeloggt!"
                
                self.showingAlert = true

                print ("Benutzer sessionToken " + sessionToken)
                

                // Den SitzungsToken in unseren Schlüsselbund schreiben
                let _: Bool = KeychainWrapper.standard.set(sessionToken, forKey: "Back4Gram.sessionToken")
            }
            // aber im Falle von GraphQL-Fehlern zeigen wir diese Meldung an
            else if let errors = graphQLResult.errors {
                // GraphQL-Fehler

                myMessage.alertTitle = "Ups!"
                myMessage.alertText = "Wir haben einen GraphQL-Fehler erhalten: " + errors.description
                self.showingAlert = true

                print(errors)
            }
        // Im Falle eines Fehlers wird die Meldung angezeigt
        case .failure(let error):
            // Netzwerk- oder Antwortformatfehler
            myMessage.alertTitle = "Ups!"
            myMessage.alertText = "Wir haben einen Fehler erhalten: " + error.localizedDescription
            self.showingAlert = true
            
            print(error)
    }
}

Testen wir es!

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

Super! Aber woher wissen wir, dass es tatsächlich funktioniert hat?
Wir können das Parse Dashboard unserer App aufrufen und prüfen, ob ein neues Session-Objekt geschrieben wurde:

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

Wunderbar!

Und wenn wir schon mal hier sind…

Wie wäre es, wenn wir einen Button zum Ausloggen hinzufügen? Nur zum Testen, damit wir wissen, dass alles reibungslos funktioniert:

Button(action: {
    // Überprüfen Sie, ob ein SessionToken gespeichert istSollte sich nur abmelden, wenn Sie eingeloggt sind.
    if (KeychainWrapper.standard.string(forKey: "Back4Gram.sessionToken") != nil) {
        print("SitzungsToken gefunden! Wir können uns abmelden.")
        
        // Ausführen der Mutation LogOutUser
        apollo.perform(mutation: LogOutUserMutation()){ result in
            // Schalten wir das Ergebnis um, damit wir ein erfolgreiches Ergebnis von einem Fehler unterscheiden können
            switch result {
                // Im Falle eines Erfolgs
                case .success(let graphQLResult):
                    // Wir versuchen, unser Ergebnis zu parsen
                    if let Ergebnis = graphQLResult.data?.users?.logOut {
                        if (Ergebnis) {
                            myMessage.alertTitle = "Juhu!"
                            myMessage.alertText = "Benutzer abgemeldet!"
                            
                            self.showingAlert = true
                            
                            // Löschen des gespeicherten SessionToken
                            let _: Bool = KeychainWrapper.standard.set("", forKey: "Back4Gram.sessionToken")
                        } else {
                            myMessage.alertTitle = "Ups!"
                            myMessage.alertText = "Die Benutzerabmeldung hat einen falschen Wert ergeben."
                            self.showingAlert = true
                        }
                    }
                    // aber im Falle von GraphQL-Fehlern zeigen wir diese Meldung an
                    else if let errors = graphQLResult.errors {
                        // GraphQL-Fehler

                        myMessage.alertTitle = "Ups!"
                        myMessage.alertText = "Wir haben einen GraphQL-Fehler erhalten: " + errors.description
                        self.showingAlert = true

                        print(errors)
                    }
                // Im Falle eines Fehlers wird die Meldung angezeigt
                case .failure(let error):
                    // Netzwerk- oder Antwortformatfehler
                    myMessage.alertTitle = "Ups!"
                    myMessage.alertText = "Wir haben einen Fehler erhalten: " + error.localizedDescription
                    self.showingAlert = true
                    
                    print(error)
            }
        }
    } else {
        // Netzwerk- oder Antwortformatfehler
        myMessage.alertTitle = "Ups!"
        myMessage.alertText = "Der Benutzer scheint nicht eingeloggt zu sein."
        self.showingAlert = true
    }
}){
    Text("Abmelden")
        .font(.headline)
        .foregroundColor(.white)
        .padding()
        .frame(Breite: 220, Höhe: 60)
        .background(hellblauFarbe)
        .cornerRadius(15.0)
}
.alert(isPresented: $showingAlert) {
    Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText)), dismissButton: .default(Text("OK")))
}

Noch einmal: Testen wir es!

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

Klasse!

Wir werden die Schaltfläche “Abmelden” später an eine andere Stelle verschieben, aber im Moment funktioniert sie, um zu zeigen, dass unser Ablauf funktioniert.

Aber was ist mit unserem Sitzungsobjekt, nachdem wir uns abgemeldet haben?

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

Automatisch weg, wie erwartet!

Fazit

Herzlichen Glückwunsch! Sie haben nun die An- und Abmeldefunktionalitäten implementiert! Und nicht nur das, Sie haben auch gelernt, wie man verschiedene Mutationen aufruft, Ergebnisse validiert und Werte im Schlüsselbund speichert! Wie genial ist das denn!

Im nächsten Kapitel werden wir mit mehreren Ansichten arbeiten und mit dem Aufbau unserer Hauptansicht beginnen!

Bleiben Sie dran!

Referenz

Melden Sie sich jetzt bei Back4App an und beginnen Sie mit der Erstellung Ihrer Instagram Clone App.

Was ist SwiftUI?

SwiftUI ist eine neue Möglichkeit, Benutzeroberflächen für Anwendungen auf Apple-Plattformen zu erstellen. Entwickler können damit die Benutzeroberfläche mithilfe von Swift-Code festlegen.

Was ist ein SessionToken?

Der von uns entwickelte Login-Prozess gibt einen SessionToken-String zurück. Dieser muss gesichert werden. Wenn der SessionToken gültig bleibt, haben wir Zugriff auf die Anwendung, andernfalls verlieren wir ihn. Dies hat mit der Sicherheit zu tun.

Was ist ein Schlüsselbund?

Wir wissen, dass das SessionToken mit der Sicherheit Ihrer App zusammenhängt. Es muss daher an einem sicheren Ort gespeichert werden. Dieser sichere Ort wird als Schlüsselbund bezeichnet. Seine Verwendung kann etwas schwierig und auch langweilig sein.


Leave a reply

Your email address will not be published.