Un clone di Instagram con SwiftUI e GraphQL

Oggi iniziamo una serie di post sul blog che vi insegneranno a utilizzare molti strumenti interessanti per costruire il vostro Social Network personale: un’applicazione che assomiglia a Instagram.

Non risparmieremo sulla tecnologia e useremo le ultime novità: Parse, GraphQL, un po’ di NodeJS e soprattutto l’ultimo framework di Apple (ancora da rilasciare) SwiftUI.

Ci vorranno alcuni post per essere completamente funzionanti, ma quando ci arriveremo, vi renderete conto di quanto possa essere semplice far funzionare le vostre idee con pochissimo sforzo qui a Back4app.

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

Quindi, sembra che sia arrivato il momento di…

Volete un inizio rapido?

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

Introduzione

Probabilmente conoscete già gli enormi vantaggi che Parse apporta al vostro processo di sviluppo, soprattutto se ospitato in Back4app, in quanto il nostro set di strumenti aumenta la produttività in misura considerevole.

Inoltre, i nostri ultimi post su GraphQL vi hanno mostrato quanto possa essere facile mantenere lo sviluppo delle API nel tempo se lo utilizzate. Se vi siete persi questi post, vi consiglio vivamente di prendervi il tempo necessario per leggerli, perché miglioreranno notevolmente la vostra comprensione.
Potete leggere di più su GraphQL e abbiamo anche un post completo su come usarlo nell’articolo Web API.
Potete anche vedere alcune integrazioni con NodeJS in Cloud Code Functions leggendo GraphQL e NodeJS.

Rimane SwiftUI.
Secondo Apple, SwiftUI è “un modo innovativo ed eccezionalmente semplice per costruire interfacce utente su tutte le piattaforme Apple con la potenza di Swift”.

A me piace pensare che SwiftUI sia molto di più.

Se, come me, sviluppate software da un po’ di tempo, probabilmente saprete che la comunità degli sviluppatori si è sempre divisa tra la comodità di utilizzare Storyboard, l’interfaccia di costruzione dell’interfaccia utente drag and drop di Apple, e la potenza dell’interfaccia utente programmatica.

SwiftUI offre il meglio di entrambi i mondi: è abbastanza facile da imparare per i principianti, pur mantenendo la manutenibilità di un’interfaccia utente codificata.
Se a ciò si aggiunge il rendering in tempo reale, che consente agli sviluppatori di vedere facilmente l’output visivo di ciò che stanno codificando, e la duplice possibilità di modificare il codice, che si rifletterà sull’interfaccia utente, e di modificare graficamente l’interfaccia utente, che si rifletterà sul codice, si ottiene uno dei modi più potenti e allo stesso tempo più semplici per fornire interfacce utente dal design accattivante e mantenibili nel tempo.

Quindi, di cosa avrete bisogno?

Al momento della stesura di questo post, Apple sta ancora testando in beta il nuovo macOS Catalina, XCode 11, iOS 13 e alcuni altri prodotti che dovrebbero arrivare presto sugli scaffali.

Sebbene sia possibile utilizzare XCode 11 con macOS Mojave, è necessario Catalina per renderizzare le anteprime di SwiftUI in tempo reale.

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

Senza Catalina? Niente anteprime per voi!

Questa è una decisione che dovete prendere voi, perché io ho scoperto che il passaggio da Bash a ZSH come shell ufficiale di macOS in Catalina ha rotto molti dei miei strumenti e script che uso per il mio flusso di lavoro quotidiano, e ho deciso di rimanere in Mojave per un po’. Sta a voi decidere se aggiornare o meno.

Inoltre, avrete bisogno di un account Back4app con un’app creata. Potete imparare a creare la vostra prima app nella documentazione di Back4app.

Infine, utilizzeremo Apollo Client per integrare GraphQL nel nostro progetto. Se non avete esperienza, ho scritto un post molto dettagliato su come configurare e utilizzare l’autenticazione web.

Quindi, prima ancora di iniziare, avrete bisogno di:

  • XCode 11 installato (con macOS Catalina se volete vedere le anteprime)
  • La vostra nuova applicazione creata in Back4app
  • Client Apollo installato e configurato

La mia app si chiamerà Back4Gram, in quanto assomiglierà a Instagram.

La nostra prima classe

Vi parlo sempre di come Parse crei la classe User, in modo da poter registrare nuovi utenti e autenticare quelli esistenti, e la classe Role, in modo da poter raggruppare gli utenti con privilegi simili. Questo è ottimo perché, almeno per l’autenticazione, non dobbiamo creare alcuna classe.

La classe User ha tre proprietà create automaticamente, di cui due obbligatorie e una opzionale:

  • nome utente (obbligatorio)
  • password (obbligatoria)
  • email (opzionale)

Queste sono sufficienti alla nostra applicazione per registrare e gestire gli utenti.

Come probabilmente saprete, la politica di Apple sulla memorizzazione delle e-mail degli utenti è molto rigida, quindi vi consiglio di non chiedere o conservare queste informazioni o potreste non essere approvati.

Poiché questo è solo un tutorial, chiederò all’utente di inserire queste informazioni, solo per farvi vedere come funziona.

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

Classi di utenti e ruoli creati automaticamente

Come se non fosse abbastanza utile, l’applicazione appena creata ha già query GraphQL e mutazioni create automaticamente per noi. Questo vale per le nostre classi create automaticamente e anche per qualsiasi altra classe creata nel tempo. La documentazione è a vostra disposizione!

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

Vi ho detto che abbiamo anche il completamento automatico? Sì, ce l’abbiamo!

Quindi, la mia prima mutazione sarà come sopra e utilizzerà la nostra specifica mutazione signUp() per creare un nuovo utente:

La query dipenderà dalla versione di Parse in uso:

Parse 3.7.2:

mutation signUpUser($username: String!, $password: String!, $email: String) {
  utenti {
    signUp(
      campi: { username: $username, password: $password, email: $email }
    ) {
      oggettoId
    }
  }
}

Parse 3.8:

mutazione signUpUser($username: String!, $password: String!, $email: String) {
    signUp(
      campi: { username: $username, password: $password, email: $email }
    ) {
      oggettoId
    }
}

Parse 3.9:

mutazione signUpUser($username: String!, $password: String!, $email: String) {
    signUp(
      campi: { username: $username, password: $password, email: $email }
    ) {
      id
    }
}

Si noti che sto rendendo le variabili username e password obbligatorie, avendo un “!” alla fine dei loro tipi, mentre email è opzionale, non avendo il “!” dopo il suo tipo.

Alla fine, restituisco l’objectId dell’utente appena creato.

E visto che siamo qui, codifichiamo anche la nostra mutazione GraphQL per il login di un utente già esistente.
In questo caso, sia il nome utente che la password sono obbligatori e recupererò il sessionToken dell’utente:

Parse 3.7.2

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

Parse 3.8

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

Parse 3.9

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

Infine, una mutazione per il logout dell’utente, che non richiede alcun parametro e restituisce solo un booleano:

Parse 3.7.2

mutazione logOutUser{
  utenti{
    logOut
  }
}

Parse 3.8

mutazione logOutUser{
    logOut
}

Parse 3.9

mutazione logOutUser{
    logOut
}

Aggiungete tutti questi file a un nuovo file chiamato UserMutations.graphql; inoltre, salvate il file schema.graphql che avete imparato a scaricare nel nostro precedente articolo.

Tenete questi file al sicuro, perché li aggiungeremo al nostro…

Progetto SwiftUI

Creiamo il nostro progetto SwiftUI.

Avviate XCode 11 e premete “Create a new XCode Project” dalla finestra Welcome to XCode.
Se non si ottiene questa finestra, si può sempre andare su File > Nuovo > Progetto.

Scegliete App iOS in alto, quindi App a vista singola. Dategli un buon nome e ricordate di scegliere Swift come linguaggio e SwiftUI come interfaccia utente:

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

Non dimenticate di selezionare SwiftUI come interfaccia utente.

Ora, installate il client Apollo come avete appreso nel nostro articolo sulle API web e aggiungete i file UserMutations.graphql e schema.graphql al vostro progetto.
Compilare e aggiungere al progetto il file API.swift appena generato.

A questo punto mi aspetto che abbiate:

  • XCode 11 installato e funzionante
  • il client Apollo installato e integrato nel progetto XCode
  • i file UserMutations.graphql e schema.graphql aggiunti al progetto XCode
  • File API.swift generato aggiunto al progetto XCode

Il progetto dovrebbe avere un aspetto simile a questo:

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

Tutto integrato in XCode 11

Ora siamo pronti per iniziare a lavorare con SwiftUI.

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

Decidere i controlli

Prima di poter effettuare il log-in o il logout di un utente, dobbiamo aver creato l’utente stesso. Per questo motivo, per prima cosa codificheremo la nostra schermata di iscrizione.

Di solito, possiamo avere 4 o 5 controlli in questa schermata:

  • Un qualche tipo di testo per dire all’utente di che schermata si tratta (testo)
  • Nome utente (controllo per l’immissione di testo)
  • Password (controllo di inserimento testo sicuro)
  • Conferma della password (opzionale, dipende dalla piattaforma, controllo di inserimento testo sicuro)
  • E-mail (controllo di inserimento testo formattato)
  • Pulsante di iscrizione (pulsante)

Poiché si tratta di un’applicazione per dispositivi mobili, ho deciso di non inserire il controllo per l’inserimento del testo di conferma della password, poiché la maggior parte delle piattaforme mobili consente all’utente di vedere l’ultimo carattere digitato e nella maggior parte dei casi ciò è sufficiente per determinare se la password è stata digitata correttamente.

Questi controlli verranno visualizzati allineati verticalmente, uno sotto l’altro, nell’ordine sopra indicato, ciascuno con il proprio colore.

Costruire effettivamente l’interfaccia utente

Come ho già detto, non sto usando macOS Catalina e quindi non avrò le anteprime abilitate, ma compilerò e mostrerò i risultati insieme al mio codice così come l’ho creato.

Create una nuova SwiftUI View andando su File > New > File e scegliendo SwiftUI View nella sezione User Interface.

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

Chiamate il file SignUpView.swift e apparirà integrato nel vostro progetto.

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

Noterete che nella nuova vista è già presente un controllo di testo con la stringa “Hello World!”.

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

Il bello di Swift è che si possono creare più viste semplici, contenenti solo i controlli necessari per quella vista, e poi unire più viste in una più complessa. È l’orientamento agli oggetti al suo meglio.

Abbiamo deciso di allineare verticalmente i nostri controlli e SwiftUI ha un controllo specifico per questo, chiamato VStack (Vertical Stack): tutto ciò che si trova all’interno di un VStack sarà automaticamente impilato verticalmente, quindi, solo per prova, aggiungiamo il controllo VStack alla nostra ContentView.swift e, al suo interno, aggiungiamo due volte la SignUpView:

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

Aggiunto due volte SignUpView nel VStack: viene visualizzato due volte allineato verticalmente.

Che bello! Se abbiamo bisogno di altri controlli, possiamo continuare ad aggiungerli!

Rimuoviamo il VStack e la duplicità e avremo una sola SignUpView() in quella View e poi torniamo alla nostra SignUpView e iniziamo a modificarla, visto che abbiamo già visto i risultati del nostro test. La vostra ContentView dovrebbe essere simile a questa:

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

Sappiamo già che avremo bisogno del VStack con Text nella parte superiore dello schermo, quindi aggiungiamo il VStack alla SignUpView e cambiamo la stringa “Hello World!” in qualcosa di utile, come “Sign Up”. La nostra SignUpView dovrebbe avere questo aspetto:

struct SignUpView: View {
    var body: some View {
        VStack{
            Text("Iscriviti")
        }
    }
}

E poiché sappiamo già quali controlli dobbiamo aggiungere al di sotto di questo, possiamo aggiungere:

  • un campo di testo per il nome utente
  • un campo sicuro per la password
  • un campo di testo per l’e-mail
  • un pulsante per l’iscrizione

Sia il TextField che il SecureField sono abbastanza simili al vecchio UITextField, ma devono fare affidamento sul binding allo stato, quindi li dichiareremo come:

Control(“Segnaposto”, text: stateValueToBindTo)

e il primo passo è dichiarare lo stato a cui legarsi:

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

Dopo aver eliminato questo aspetto, possiamo iniziare ad aggiungere i nostri controlli:

Text("Iscriviti")
Campo di testo("Nome utente", testo: $nome utente)
SecureField("Password", testo: $password)
Campo di testo("Email (opzionale)", testo: $email)

Infine, possiamo aggiungere il nostro pulsante, che ha questa struttura:

Button(action: {
//Codice da eseguire quando viene attivato
}){
//Contenuto
}

Come avrete notato, funziona come un UIButton, ma la sua struttura è molto più flessibile, in quanto possiamo definire il suo contenuto in modo programmatico, aggiungendo praticamente qualsiasi cosa: un testo, un’immagine. Qualsiasi cosa.

La vecchia struttura target/action è scomparsa, sostituita da una nuova struttura action:{} per gestire il comportamento quando viene toccato.

Il nostro pulsante avrà un testo che dice “Iscriviti!”, quindi aggiungiamolo:

Button(action: {

}){
    Text("Iscriviti!")
}

Il codice finale dovrebbe assomigliare a questo:

struct SignUpView: View {
    @State var username: String = ""
    @State var password: String = ""
    @State var email: String = ""
    
    var body: some View {
        VStack{
            Testo("Iscriviti")
            TextField("Nome utente", testo: $nomeutente)
            SecureField("Password", testo: $password)
            TextField("Email (opzionale)", testo: $email)
            Button(action: {
                
            }){
                Text("Iscriviti!")
            }
        }
    }
}

E che dire della nostra anteprima?

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

Sembra promettente, ma possiamo migliorare molto impostando alcune proprietà visive.
E se c’è qualcosa di superlativo in SwiftUI è che si possono aggiungere proprietà visive al codice e vedere le modifiche in tempo reale!
Proviamo!

Per prima cosa, dichiariamo i colori che utilizzeremo

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

Poi aggiungiamo alcune caratteristiche interessanti:

Un po’ di padding, in modo da avere un po’ di spazio tra questi elementi.
Impostare le proprietà background e foregroundColor in modo da avere controlli coerenti con il nostro marchio.
Un po’ di cornerRadius, in modo da avere angoli arrotondati.
E impostare font e fontWeight per rendere le cose più gradevoli.
Voilà!

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

È bello o no?
Solo per tenere traccia del nostro codice a questo punto:

struct SignUpView: View {
    @State var username: String = ""
    @State var password: String = ""
    @State var email: String = ""
    
    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)

    
    var body: some View {
        VStack{
            Testo("Iscriviti")
                .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)
            Campo di testo("Email (opzionale)", testo: $email)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            Button(action: {
                
            }){
                Testo("Iscriviti!")
                 .font(.headline)
                 .foregroundColor(.white)
                 .padding()
                 .frame(larghezza: 220, altezza: 60)
                 .background(lightBlueColor)
                 .cornerRadius(15.0)
            }
        }.padding()
    }
}

Quindi, la nostra bella schermata lucida è quasi pronta.
Che ne dite di…

Alcune funzionalità

Ora che la nostra interfaccia utente è pronta, è il momento di aggiungere le chiamate Apollo ai nostri metodi GraphQL.

Poiché useremo queste chiamate in quasi tutte le viste, dobbiamo creare un client Apollo in un luogo in cui tutte le viste possano accedervi. Questo posto è AppDelegate.swift.

Dichiariamo il nostro client Apollo in questo file, sotto l’elemento

importare UIKit

e l’inizio del blocco di codice

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

proprio come vi abbiamo mostrato nel nostro precedente articolo, e d’ora in poi tutte le viste potranno eseguire query e mutazioni GraphQL.
Bisogna prima importare il framework Apollo e poi istanziare il client in questo modo:

importare Apollo

// Inizializzazione del client Apollo.
// Per saperne di più: 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(stringa: "https://parseapi.back4app.com/graphql")!
    
    return ApolloClient(
        networkTransport: HTTPNetworkTransport(
            url: url,
            configurazione: configurazione
        )
    )
}()

Basta sostituire le stringhe YourAppIdHere e YourClientKeyHere con i valori attuali e possiamo andare avanti.

Come probabilmente avrete intuito, dobbiamo eseguire la nostra mutazione per SignUp quando clicchiamo sul pulsante Sign Up, quindi, nel blocco azione, chiamiamolo:

            // Eseguiamo la mutazione SignUpUser, passando i parametri che abbiamo appena ottenuto dai nostri campi di testo
            apollo.perform(mutazione: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ 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 nostro risultato
                        if let objId = graphQLResult.data?.users?.signUp.objectId {
                            print ("Utente creato con ObjectId: " + objId)
                        }
                        // ma in caso di errori GraphQL presentiamo questo messaggio
                        else if let errors = graphQLResult.errors {
                            // Errori GraphQL
                            print(errori)
                            
                        }
                    // In caso di fallimento, presentiamo il messaggio
                    case .failure(let error):
                      // Errori di rete o di formato della risposta
                      print(errore)
                }
            }

Questo funzionerà, ma i metodi print() stamperanno solo sulla console. Abbiamo bisogno di presentare un avviso all’utente, quindi cambiamolo in un Alert, con la seguente struttura:

Alert(title: Text(“Titolo”), message: Text(“Messaggio”), dismissButton: .default(Text(“TextOfButton”))

Poiché avremo bisogno di modificare il titolo e il messaggio in fase di esecuzione, dobbiamo impostare una struct per gestire questi valori, poiché le variabili self non possono essere modificate in fase di esecuzione:

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

var myMessage = Messaggio()

E creiamo anche uno Stato, in modo che la vista possa sapere quando mostrare l’avviso:

@State private var showingAlert = false

Il codice completo per l’azione Button dovrebbe essere così:

Button(action: {
            // Eseguiamo la mutazione SignUpUser, passando i parametri che abbiamo appena ottenuto dai nostri campi di testo
            apollo.perform(mutazione: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result 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 nostro risultato
                        if let objId = graphQLResult.data?.users?.signUp.objectId {
                            myMessage.alertTitle = "Yay!"
                            myMessage.alertText = "L'utente si è iscritto!"
                            
                            self.showingAlert = true

                            print ("Utente creato con ObjectId: " + objId)
                        }
                        // 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)
                }
            }
           }){
               Testo("Iscriviti!")
                .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")))
           }

Sembra promettente, eh?

Quindi è il momento di compilare ed eseguire

Compilare ed eseguire. Provate a registrare un nuovo utente e dovreste ottenere…

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

Vediamo se l’utente viene creato nella nostra Parse Dashboard!

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

Conclusione

Abbiamo creato una vista SwiftUI perfettamente funzionante che esegue mutazioni GraphQL utilizzando Apollo Client. Che meraviglia!

Continueremo a lavorare sulla nostra app clone di Instagram! I prossimi passi sono il log in e il log out e il post è già in forno!

Restate sintonizzati!

Riferimento

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


Leave a reply

Your email address will not be published.