Um clone do Instagram usando SwiftUI e GraphQL – Login

Em nossa postagem anterior sobre como criar um aplicativo clone do Instagram, você aprendeu a configurar tudo para ter o SwiftUI em funcionamento no XCode 11 e criou uma visualização de Sing Up totalmente funcional com GraphQL.

Hoje, aprenderemos a criar uma visualização de login e a fazer com que o usuário faça logout.

Precisaremos do projeto da postagem anterior, portanto, se você não acompanhou a postagem anterior, sugiro enfaticamente que o faça.

Apertem os cintos de segurança e vamos lá!

Para um melhor aprendizado, faça o download do projeto iOS Instagram Clone com o código-fonte.

Deseja um início rápido?

Clone esse aplicativo em nosso Hub e comece a usá-lo sem problemas!

Criação da visualização de login

Nossa visualização de login será bastante semelhante à visualização de inscrição, na verdade, ainda mais simples.

Em nosso logInUser Mutation, precisamos apenas de dois parâmetros: nome de usuário e senha:

As consultas e mutações dependerão da versão do Parse que você escolheu:

Parse 3.7.2:

mutação logInUser($username: String!, $password: String!){
  usuários{
    logIn(nome de usuário: $username, senha: $password){
      sessionToken
    }
  }
}

Parse 3.8:

mutação logInUser($username: String!, $password: String!){
    logIn(username: $username, password: $password){
      sessionToken
    }
}

Parse 3.9:

mutação logInUser($username: String!, $password: String!){
    logIn(username: $username, password: $password){
      sessionToken
    }
}

portanto, só precisaremos perguntar isso para os nossos usuários.

Vamos começar adicionando uma nova visualização SwiftUI, indo para File > New > File e selecionando nossa visualização SwiftUI

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

Vamos nomear essa visualização como LogInView.swift e adicioná-la ao nosso projeto:

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

E, como você já aprendeu, crie sua VStack com os controles de que precisaremos:

  • TextField para o nome de usuário
  • SecureField para a senha
  • Botão para executar a ação

Para manter a consistência do design, movi as cores que usaremos para o AppDelegate.swift, portanto, tive que importar o SwiftUI para lá também:

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)

Lembre-se de remover as linhas de cor do SignUpView.swift e do LogInView.swift.

Além disso, para manter a consistência dos controles, apenas copiei e colei da nossa visualização SignUp e removi o TextField de e-mail e alterei os TextFields para refletir as novas funcionalidades. Meu código ficou assim:

struct LogInView: View {
    @State var nome de usuário: 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("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)
           Button(action: {
            
           }){
               Texto("Log In!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(largura: 220, altura: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
       }.padding()
    }
}

Isso foi simples. Vamos ver como fica?
Altere seu ContentView.swift para mostrar essa visualização:

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

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

Ficou bem legal!

Vamos deixá-lo mais organizado adicionando nosso logotipo na parte superior!
Nosso logotipo consistirá em uma imagem que será integrada ao nosso projeto. Eu usei esta aqui.

Arraste e solte essa imagem em sua pasta Assets.xcassets no projeto:

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

Ela deverá ter a seguinte aparência:

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

De agora em diante, podemos fazer referência a ela em nosso código com esse nome: logo-social.

Inscreva-se agora no Back4App e comece a criar seu aplicativo clone do Instagram.

Nosso logotipo

Nosso logotipo consistirá nessa imagem, mas simplesmente colocar a imagem ali pareceria amador. Nós o faremos brilhar: circular, com um tamanho fixo, alguns traços nas bordas e, é claro, sombra projetada por causa da… sombra projetada.

O código para tudo isso é o seguinte:

Imagem("logo-social")
    .redimensionável()
    .aspectRatio(contentMode: .fit)
    .frame(largura: 150, altura: 150)
    .clipShape(Circle())
    .overlay(Circle().stroke(Color.blue, lineWidth: 2))
    .shadow(radius: 5)
    .padding(.bottom, 75)

E ele vai para o topo da nossa VStack:

var body: some View {
       VStack{
           Imagem("logo-social")
                .redimensionável()
                .aspectRatio(contentMode: .fit)
                .frame(width: 150, height: 150)
                .clipShape(Circle())
                .overlay(Circle().stroke(Color.blue, lineWidth: 2))
                .shadow(radius: 5)
                .padding(.bottom, 75)
           Texto ("Log In")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
...

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

Não é lindo?

O sessionToken

Nosso processo de login retornará uma string sessionToken. Devemos manter esse sessionToken seguro, pois ele será usado durante nossas operações: enquanto esse sessionToken for válido, teremos acesso ao nosso aplicativo. Quando ele for excluído ou invalidado, nossas chamadas serão negadas.

Como isso está diretamente relacionado à segurança, precisaremos armazená-lo de forma segura. O local correto para fazer isso é o Keychain no iOS.

O único problema do Keychain é que ele é difícil e chato de usar, por isso decidi usar esse wrapper para gerenciá-lo. Ele torna a vida muito mais fácil e, como o Keychain é um aplicativo de segurança, ele é muito fácil de usar. Isso facilita muito a vida e, como já estamos usando Cocoapods, faz todo o sentido.

Vamos editar nosso Podfile e adicionar isso aos nossos pods:

pod 'SwiftKeychainWrapper'

Em seguida, vamos atualizar nossos pods com o comando

pod install

e, por fim, reabrir nosso projeto xcworkspace.

Agora estamos prontos para usar nosso mecanismo Keychain para…

Armazenar o sessionToken

De acordo com a documentação do nosso novo pod, a maneira de salvar um valor no keychain é

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

mas vamos adicionar alguma lógica a ele também.

Para começar, vamos importar nosso wrapper:

importar SwiftKeychainWrapper

A primeira coisa é que teremos de chamar nossa mutação logInUser e, quando ela responder, armazenaremos o sessionToken, se houver um. Caso contrário, teremos de notificar o usuário com um alerta.

Agora, se você se lembra do artigo anterior, já temos um alerta codificado, incluindo sua estrutura. Vamos reutilizá-lo removendo o código abaixo do nosso SingUpView.swift e passando-o para o nosso arquivo AppDelegate.swift para que todas as exibições possam acessá-lo:

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

var myMessage = Message()

Agora, voltando à nossa lógica, a primeira coisa que temos de fazer é determinar se o usuário preencheu as caixas de texto de nome de usuário e senha. Caso contrário, não há informações para o processo de login e devemos notificar o usuário sobre isso.

Nosso código de ação para o botão de login deve verificar isso. Vamos adicionar esse trecho de código que verifica o tamanho da string das variáveis de estado vinculadas a esses campos de texto:

           Button(action: {
            // Verificar se há uma senha digitada
            if (self.password.count == 0 || self.username.count == 0){
                
                // Caso contrário, devemos mostrar o alerta
                myMessage.alertText = "Você deve fornecer um nome de usuário e uma senha".
                myMessage.alertTitle = "Oops..."
                self.showingAlert = true
            } else {
                // Se sim, podemos prosseguir
                
            }
           }){
               Text("Log In!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(largura: 220, altura: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
           .alert(isPresented: $showingAlert) {
               Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText), dismissButton: .default(Text("OK"))))
           }

e testá-lo…

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

Legal!

Agora precisamos chamar nossa mutação GraphQL para recuperar o sessionToken e, se obtivermos algum, armazená-lo no Keychain.
Você já aprendeu como chamar as mutações, então vamos fazer isso, mas, desta vez, se obtivermos o sessionToken, vamos armazená-lo:

// Executar a mutação LogInUser, passando os parâmetros que acabamos de obter de nossos TextFields
apollo.perform(mutation: LogInUserMutation(username: self.username, password: self.password)){ result in
    // Vamos alternar o resultado para que possamos separar um resultado bem-sucedido de um erro
    switch result {
        // Em caso de sucesso
        case .success(let graphQLResult):
            // Tentamos Parse nosso resultado
            if let sessionToken = graphQLResult.data?.users?.logIn.sessionToken {
                myMessage.alertTitle = "Yay!"
                myMessage.alertText = "O usuário se conectou!"
                
                self.showingAlert = true

                print ("User sessionToken " + sessionToken)
                

                // Gravar o sessionToken em nosso Keychain
                let _: Bool = KeychainWrapper.standard.set(sessionToken, forKey: "Back4Gram.sessionToken")
            }
            // mas em caso de erros do GraphQL, apresentamos essa mensagem
            else if let errors = graphQLResult.errors {
                // Erros do GraphQL

                myMessage.alertTitle = "Oops!"
                myMessage.alertText = "Temos um erro de GraphQL: " + errors.description
                self.showingAlert = true

                print(errors)
            }
        // Em caso de falha, apresentamos essa mensagem
        case .failure(let error):
            // Erros de rede ou de formato de resposta
            myMessage.alertTitle = "Oops!"
            myMessage.alertText = "Ocorreu um erro: " + error.localizedDescription
            self.showingAlert = true
            
            print(error)
    }
}

Vamos testá-lo!

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

Que legal! Mas como sabemos se realmente funcionou?
Podemos acessar o Parse Dashboard do nosso aplicativo e verificar se há um novo objeto Session gravado:

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

Como um encanto!

E já que estamos aqui…

Que tal adicionar um botão para fazer o logout? Apenas para teste, para que saibamos que tudo está tranquilo:

Button(action: {
    // Verificar se há sessionToken storeStringhould only log out if logged in.
    if (KeychainWrapper.standard.string(forKey: "Back4Gram.sessionToken") != nil) {
        print("Encontrado o sessionToken! Podemos fazer logout.")
        
        // Executar a mutação LogOutUser
        apollo.perform(mutation: LogOutUserMutation()){ result in
            // Vamos alternar o resultado para que possamos separar um resultado bem-sucedido de um erro
            switch result {
                // Em caso de sucesso
                case .success(let graphQLResult):
                    // Tentamos Parse nosso resultado
                    if let result = graphQLResult.data?.users?.logOut {
                        if (result) {
                            myMessage.alertTitle = "Yay!"
                            myMessage.alertText = "O usuário efetuou logout!"
                            
                            self.showingAlert = true
                            
                            // Limpar o sessionToken armazenado
                            let _: Bool = KeychainWrapper.standard.set("", forKey: "Back4Gram.sessionToken")
                        } else {
                            myMessage.alertTitle = "Oops!"
                            myMessage.alertText = "A operação de logout do usuário retornou False."
                            self.showingAlert = true
                        }
                    }
                    // mas, no caso de qualquer erro do GraphQL, apresentamos essa mensagem
                    else if let errors = graphQLResult.errors {
                        // Erros do GraphQL

                        myMessage.alertTitle = "Oops!"
                        myMessage.alertText = "Temos um erro de GraphQL: " + errors.description
                        self.showingAlert = true

                        print(errors)
                    }
                // Em caso de falha, apresentamos essa mensagem
                case .failure(let error):
                    // Erros de rede ou de formato de resposta
                    myMessage.alertTitle = "Oops!"
                    myMessage.alertText = "Ocorreu um erro: " + error.localizedDescription
                    self.showingAlert = true
                    
                    print(error)
            }
        }
    } else {
        // Erros de formato de rede ou de resposta
        myMessage.alertTitle = "Oops!"
        myMessage.alertText = "O usuário não parece estar conectado".
        self.showingAlert = true
    }
}){
    Texto ("Logout")
        .font(.headline)
        .foregroundColor(.white)
        .padding()
        .frame(largura: 220, altura: 60)
        .background(lightBlueColor)
        .cornerRadius(15.0)
}
.alert(isPresented: $showingAlert) {
    Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText), dismissButton: .default(Text("OK"))))
}

Mais uma vez, vamos testá-lo!

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

Que legal!

Moveremos o botão Log Out para outro lugar mais tarde, mas, por enquanto, ele funciona para mostrar que o nosso fluxo está funcionando.

Mas o que acontece com o objeto Session agora que saímos do sistema?

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

Desapareceu automaticamente, como esperado!

Conclusão

Parabéns! Agora você tem as funcionalidades de login e logout implementadas! Além disso, você aprendeu a chamar diferentes mutações, validar resultados e armazenar valores no Keychain! Isso é incrível!

No próximo capítulo, começaremos a trabalhar com várias exibições e a criar nossa exibição principal!

Fique ligado!

Referência

Registre-se agora no Back4App e comece a criar seu aplicativo clone do Instagram.

O que é SwiftUI?

SwiftUI é uma nova maneira de criar interfaces de usuário para aplicativos em plataformas Apple. Ela permite que os desenvolvedores determinem a interface do usuário usando o código Swift.

O que é um sessionToken?

O processo de login que estamos desenvolvendo retornará uma string sessionToken. Ela precisa ser protegida. Se conseguirmos manter o sessionToken válido, teremos acesso ao aplicativo; caso contrário, perderemos o acesso. Isso está relacionado à segurança.

O que é o Keychain?

Sabemos que o sessionToken está relacionado à segurança do seu aplicativo. Portanto, ele precisa ser armazenado em um local seguro. Esse local seguro é chamado de keychain. Pode ser um pouco complicado de usar e também pode ser entediante.


Leave a reply

Your email address will not be published.