Um clone do Instagram usando SwiftUI e GraphQL
Hoje estamos iniciando uma série de postagens no blog que o ensinarão a usar várias ferramentas interessantes para criar sua própria rede social: um aplicativo semelhante ao Instagram.
Não economizaremos em tecnologia e usaremos o que há de melhor e mais recente: Parse, GraphQL, um pouco de NodeJS e, especialmente, o (ainda a ser lançado) mais recente framework da Apple, o SwiftUI.
Isso levará algumas postagens para ser totalmente funcional, mas quando chegarmos lá, você perceberá como pode ser simples colocar suas ideias em funcionamento com muito pouco esforço aqui na Back4app.
Para aprender melhor, faça o download do projeto iOS Instagram Clone com o código-fonte.
Então, parece que chegou a hora de…
Contents
Você quer começar rapidamente?
Clone esse aplicativo em nosso Hub e comece a usá-lo sem problemas!
Algumas introduções
Bem, você provavelmente já conhece os enormes benefícios que o Parse traz para o seu processo de desenvolvimento, especialmente se hospedado no Back4app, pois nosso conjunto de ferramentas aumenta a produtividade em grandes quantidades.
E nossos últimos posts sobre GraphQL mostraram como pode ser fácil manter o desenvolvimento de APIs ao longo do tempo se você usá-lo. Se você perdeu essas publicações, sugiro enfaticamente que reserve um tempo para lê-las, pois elas melhorarão muito sua compreensão à medida que avançamos.
Você pode ler mais sobre o GraphQL e também temos uma postagem completa sobre como usá-lo no artigo API da Web.
Você também pode ver algumas integrações com o NodeJS no Cloud Code Functions, lendo GraphQL e NodeJS.
Isso nos deixa com a SwiftUI.
O SwiftUI é, de acordo com a Apple, “uma maneira inovadora e excepcionalmente simples de criar interfaces de usuário em todas as plataformas da Apple com o poder do Swift”.
Eu mesmo gosto de pensar que a SwiftUI é mais do que isso.
Se você, como eu, está desenvolvendo software há algum tempo, provavelmente sabe que a comunidade de desenvolvedores sempre esteve dividida entre a conveniência de usar Storyboards, a interface de construção de UI de arrastar e soltar da Apple, ou o poder da UI programática.
A SwiftUI vem trazer o melhor dos dois mundos: é fácil o suficiente para os iniciantes aprenderem, mas mantém a capacidade de manutenção de uma interface de usuário codificada.
Combine isso com a renderização em tempo real, para que os desenvolvedores possam ver facilmente o resultado visual do que estão codificando, e faça com que seja bidirecional, de modo que, se você alterar o código, isso se refletirá na interface do usuário e, se alterar graficamente a interface do usuário, isso se refletirá no código, e você terá uma das maneiras mais poderosas e fáceis de fornecer interfaces de usuário com design bonito e de fácil manutenção ao longo do tempo.
Então, do que você vai precisar?
No momento em que escrevo este post, a Apple ainda está testando a versão beta do novo macOS Catalina, o XCode 11, o iOS 13 e alguns outros produtos que devem chegar às prateleiras em breve.
Embora seja possível usar o XCode 11 com o macOS Mojave, você precisará do Catalina para renderizar as visualizações da SwiftUI em tempo real.
Sem o Catalina? Nada de visualizações para você!
Essa é uma decisão que você precisa tomar, pois descobri que a mudança de Bash para ZSH como shell oficial do macOS no Catalina quebrou muitas das minhas ferramentas e scripts que uso no meu fluxo de trabalho diário. Cabe a você decidir se vai atualizar ou não.
Além disso, você precisará de uma conta Back4app com um aplicativo criado. Você pode aprender a criar seu primeiro aplicativo na documentação do Back4app.
Por último, mas não menos importante, usaremos o Apollo Client para integrar o GraphQL ao nosso projeto. Se você não tem experiência com ele, escrevi um post bem detalhado sobre como configurá-lo e usá-lo para autenticação na Web.
Portanto, antes mesmo de começarmos, você precisará de
- XCode 11 instalado (com o macOS Catalina se você quiser ver as visualizações)
- Seu novo aplicativo criado no Back4app
- Cliente Apollo instalado e configurado
Meu aplicativo se chamará Back4Gram, pois será parecido com o Instagram.
Nossa primeira aula
Eu sempre falo para vocês sobre como o Parse cria a classe User, para que você possa cadastrar novos usuários e autenticar os existentes, e a classe Role, para que você possa agrupar usuários com privilégios semelhantes. Isso é ótimo porque, pelo menos para autenticação, não precisamos criar nenhuma classe.
A classe User tem três propriedades criadas automaticamente, duas delas obrigatórias e uma opcional:
- nome de usuário (obrigatório)
- senha (obrigatória)
- e-mail (opcional)
Essas propriedades são suficientes para que nosso aplicativo registre e gerencie usuários.
Como você provavelmente sabe, a política da Apple sobre o armazenamento de e-mails de usuários é muito rígida, portanto, recomendo não solicitar nem manter essas informações, ou você poderá não ser aprovado.
Como este é apenas um tutorial, pedirei ao usuário que insira essas informações, apenas para que você possa ver como funciona.
Classes de usuário e função criadas automaticamente
Como se isso não fosse útil o suficiente, seu aplicativo recém-criado já tem consultas e mutações GraphQL criadas automaticamente para nós. Isso se aplica às nossas classes criadas automaticamente e também a qualquer outra classe que você criar ao longo do tempo. Também temos a documentação para você!
Eu lhe disse que também temos o Autocomplete? Sim, temos!
Portanto, minha primeira mutação será como acima e usará nossa mutação específica signUp() para criar um novo usuário:
A consulta dependerá da versão do Parse que você estiver usando:
Parse 3.7.2:
mutation signUpUser($username: String!, $password: String!, $email: String) { usuários { signUp( fields: { username: $username, password: $password, email: $email } ) { objectId } } }
Parse 3.8:
mutação signUpUser($username: String!, $password: String!, $email: String) { signUp( fields: { username: $username, password: $password, email: $email } ) { objectId } }
Parse 3.9:
mutação signUpUser($username: String!, $password: String!, $email: String) { signUp( fields: { username: $username, password: $password, email: $email } ) { id } }
Observe que estou tornando minhas variáveis nome de usuário e senha obrigatórias por ter um “!” no final de seus tipos, enquanto o e-mail é opcional, por não ter o “!” após seu tipo.
No final, estou retornando o objectId do usuário recém-criado.
E já que estamos aqui, vamos também codificar nossa mutação GraphQL para fazer login em um usuário já existente.
Nesse caso, tanto o nome de usuário quanto a senha são obrigatórios, e eu recuperarei o sessionToken do usuário:
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 } }
Por último, mas não menos importante, uma mutação para o usuário fazer logout, que não requer nenhum parâmetro e retorna apenas um booleano:
Parse 3.7.2
mutação logOutUser{ usuários{ logOut } }
Parse 3.8
mutação logOutUser{ logOut }
Parse 3.9
mutação logOutUser{ logOut }
Adicione todos esses arquivos a um novo arquivo chamado UserMutations.graphql e salve também o arquivo schema.graphql que você aprendeu a baixar em nosso artigo anterior.
Mantenha esses arquivos seguros, pois os adicionaremos ao nosso…
Projeto SwiftUI
Vamos criar nosso projeto SwiftUI.
Inicie o XCode 11 e clique em “Create a new XCode Project” na janela Welcome to XCode.
Se essa janela não for exibida, você poderá ir para File > New > Project.
Escolha iOS App na parte superior e, em seguida, Single View App. Dê a ele um bom nome e lembre-se de escolher Swift como a linguagem e SwiftUI como a interface do usuário:
Não se esqueça de selecionar SwiftUI como a interface do usuário
Agora, instale o Apollo Client como você aprendeu em nosso artigo sobre API da Web e, depois disso, adicione os arquivos UserMutations.graphql e schema.graphql ao seu projeto.
Compile e adicione o arquivo API.swift recém-gerado ao projeto.
Neste ponto, o que espero que você tenha é:
- XCode 11 instalado e em execução
- O cliente Apollo instalado e integrado em seu projeto XCode
- Arquivos UserMutations.graphql e schema.graphql adicionados ao seu projeto XCode
- Arquivo gerado API.swift adicionado ao seu projeto XCode
Seu projeto deve ser semelhante a este:
Tudo integrado ao XCode 11
Agora estamos prontos para começar a usar o SwiftUI.
Inscreva-se agora no Back4App e comece a criar seu aplicativo clone do Instagram.
Decidindo nossos controles
Antes de podermos fazer login ou logout de um usuário, precisamos ter esse usuário criado em primeiro lugar. Portanto, primeiro vamos codificar nossa tela SignUp.
Normalmente, podemos ter de 4 a 5 controles nessa tela:
- Algum tipo de texto para informar ao usuário que tela é essa (texto)
- Nome de usuário (controle de entrada de texto)
- Senha (controle de entrada de texto seguro)
- Confirmar senha (opcional, depende da plataforma, controle de entrada de texto seguro)
- E-mail (controle de entrada de texto formatado)
- Botão Sign Up (botão)
Como este será um aplicativo móvel, decidi não colocar o controle de entrada de texto Confirm Password (Confirmar senha), pois a maioria das plataformas móveis permite que o usuário veja o último caractere digitado e, na maioria dos casos, isso é suficiente para que o usuário determine se a senha está sendo digitada corretamente.
Esses controles serão exibidos alinhados verticalmente, um abaixo do outro, na ordem acima, cada um com sua própria cor.
Criando de fato a interface do usuário
Como mencionei anteriormente, não estou usando o macOS Catalina, portanto não terei as visualizações habilitadas, mas compilarei e mostrarei os resultados juntamente com o meu código como eu o tinha.
Crie uma nova visualização SwiftUI acessando File > New > File e, em seguida, escolhendo SwiftUI View na seção User Interface.
Dê a esse arquivo o nome de SignUpView.swift e ele aparecerá integrado ao seu projeto.
Você perceberá que a visualização recém-criada já tem um controle de texto com a string “Hello World!”.
A beleza do Swift é que você pode criar várias exibições simples, contendo apenas os controles necessários para essa exibição, e depois mesclar várias exibições em uma mais complexa. É a orientação a objetos em sua melhor forma.
Decidimos alinhar nossos controles verticalmente, e a SwiftUI tem um controle específico para isso chamado VStack (Vertical Stack): tudo o que estiver dentro de um VStack será automaticamente empilhado verticalmente. Então, apenas para testar, vamos adicionar nosso controle VStack ao ContentView.swift e, dentro dele, adicionar duas vezes o SignUpView:
Adicionamos o SignUpView duas vezes no VStack: ele aparece duas vezes alinhado verticalmente
Que legal isso! Se precisarmos de mais controles, podemos continuar adicionando-os!
Vamos remover o VStack e a duplicidade e ter apenas um SignUpView() nessa visualização e, em seguida, voltar ao nosso SignUpView e começar a alterá-lo, pois já vimos os resultados do nosso teste. Seu ContentView deve ser semelhante a este agora:
struct ContentView: View { var body: some View { SignUpView() } }
Já sabemos que precisaremos dessa VStack com Text na parte superior da tela, portanto, vamos adicionar a VStack ao SignUpView e alterar a string “Hello World!” para algo útil, como “Sign Up”. Nosso SignUpView deve ter a seguinte aparência:
struct SignUpView: View { var body: some View { VStack{ Text("Sign Up") } } }
E como já sabemos quais controles devemos adicionar abaixo disso, podemos adicionar:
- um TextField para o nome de usuário
- um SecureField para a senha
- um TextField para Email
- um botão para registrar-se
O TextFields e o SecureField são bastante semelhantes ao antigo UITextField, mas precisam depender da vinculação ao estado, portanto, vamos declará-los como:
Control(“Placeholder”, text: stateValueToBindTo)
e a primeira etapa é declarar o estado ao qual vincular:
@State var nome de usuário: String = "" @State var password: String = "" @State var email: String = ""
Com isso resolvido, podemos começar a adicionar nossos controles:
Text("Sign Up") TextField("Username", text: $username) SecureField("Password", text: $password) TextField("Email (opcional)", text: $email)
Por último, mas não menos importante, podemos adicionar nosso botão, que tem esta estrutura:
Button(action: {
//Código a ser executado quando acionado
}){
//Conteúdo
}
Como você deve ter notado, ele funciona da mesma forma que um UIButton, mas sua estrutura é muito mais flexível, pois podemos definir seu conteúdo programaticamente, adicionando praticamente qualquer coisa a ele: um texto, uma imagem. O que você quiser.
A antiga estrutura target/action desapareceu, sendo substituída por uma nova estrutura action:{} para lidar com o comportamento quando tocada.
Nosso botão terá um texto dizendo “Sign Up!” (Registre-se!), portanto, vamos adicioná-lo:
Button(action: { }){ Text("Sign Up!") }
E seu código final deverá ter a seguinte aparência:
struct SignUpView: View { @State var nome de usuário: String = "" @State var password: String = "" @State var email: String = "" var body: some View { VStack{ Text("Sign Up") TextField("Username", text: $username) SecureField("Password", text: $password) TextField("Email (opcional)", text: $email) Botão(ação: { }){ Text("Sign Up!") } } } }
E quanto à nossa visualização?
Parece promissora, mas podemos melhorar muito definindo algumas propriedades visuais.
E se há algo superlegal na SwiftUI é que você pode adicionar propriedades visuais ao código e ver as alterações em tempo real também!
Vamos experimentar algumas!
Primeiro, vamos declarar as cores que usaremos
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)
Em seguida, vamos adicionar alguns recursos interessantes:
Um pouco de Padding para que tenhamos algum espaço entre esses elementos.
Defina as propriedades background e foregroundColor para que tenhamos controles que sejam consistentes com a nossa marca.
Algumas cornerRadius para que possamos ter cantos arredondados.
E definir font e fontWeight para deixar as coisas mais bonitas.
Voilà!
Ficou legal ou não?
Só para que você possa acompanhar o nosso código neste ponto:
struct SignUpView: View { @State var nome de usuário: String = "" @State var password: String = "" @State var email: String = "" 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) var body: some View { VStack{ Text("Sign Up") .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) TextField("Email (opcional)", text: $email) .padding() .background(lightGreyColor) .cornerRadius(5.0) .padding(.bottom, 20) Botão(action: { }){ Texto("Sign Up!") .font(.headline) .foregroundColor(.white) .padding() .frame(largura: 220, altura: 60) .background(lightBlueColor) .cornerRadius(15.0) } }.padding() } }
Portanto, temos nossa tela brilhante e bonita quase pronta.
Que tal…
Alguma funcionalidade
Agora que temos nossa interface do usuário pronta, é hora de adicionar as chamadas do Apollo aos nossos métodos GraphQL.
Como vamos usar essas chamadas em quase todas as visualizações, devemos criar um cliente Apollo em um local onde todas as visualizações possam acessá-lo. Esse local é o AppDelegate. Esse local é o AppDelegate.swift.
Declare seu cliente Apollo lá, abaixo da seção
import UIKit
e o início do bloco de código
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { ...
como mostramos em nosso artigo anterior e, a partir de agora, todas as visualizações podem executar consultas e mutações GraphQL.
Primeiro, você precisa importar a estrutura Apollo e, em seguida, instanciar seu cliente desta forma:
importar Apollo // Inicialização do cliente Apollo. Mais sobre isso aqui: 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(string: "https://parseapi.back4app.com/graphql")! return ApolloClient( networkTransport: HTTPNetworkTransport( url: url, configuration: configuration ) ) }()
Basta substituir as cadeias de caracteres YourAppIdHere e YourClientKeyHere por seus valores atuais e podemos continuar.
Como você provavelmente adivinhou, devemos executar nossa mutação para SignUp quando clicarmos no botão Sign Up, portanto, no bloco de ação, vamos chamá-la:
// Executar a mutação SignUpUser, passando os parâmetros que acabamos de obter de nossos TextFields apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ 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 objId = graphQLResult.data?.users?.signUp.objectId { print ("Usuário criado com ObjectId: " + objId) } // mas no caso de erros do GraphQL, apresentamos essa mensagem else if let errors = graphQLResult.errors { // Erros do GraphQL print(errors) } // Em caso de falha, apresentamos a mensagem case .failure(let error): // Erros de rede ou de formato de resposta print(error) } }
Isso funcionará, mas esses métodos print() só serão impressos no console. Precisamos apresentar um alerta ao nosso usuário, portanto, vamos alterá-lo para um alerta, com a seguinte estrutura:
Alert(title: Text(“Title”), message: Text(“Message”), dismissButton: .default(Text(“TextOfButton”)))
Como precisaremos alterar o título e a mensagem em tempo de execução, devemos definir uma Struct para lidar com esses valores, pois as variáveis próprias não podem ser alteradas em tempo de execução:
struct Message { var alertTitle: String = "" var alertText: String = "" } var myMessage = Message()
E também crie um estado para que a visualização possa saber quando mostrar o alerta:
@State private var showingAlert = false
E nosso código completo para a ação do botão deve ser assim:
Button(action: { // Executar a mutação SignUpUser, passando os parâmetros que acabamos de obter de nossos TextFields apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ 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 objId = graphQLResult.data?.users?.signUp.objectId { myMessage.alertTitle = "Yay!" myMessage.alertText = "O usuário se inscreveu!" self.showingAlert = true print ("Usuário criado com ObjectId: " + objId) } // 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) } } }){ Texto("Registre-se!") .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")))) }
Parece promissor, não é?
Então chegou a hora
Compilar e executar. Tente registrar um novo usuário e você deverá obter…
Vamos ver se o usuário foi criado em nosso Parse Dashboard!
Conclusão
Você criou uma visualização SwiftUI totalmente funcional que executa mutações GraphQL usando o Apollo Client. Isso é incrível!
Vamos continuar trabalhando em nosso aplicativo clone do Instagram! As próximas etapas são fazer login e sair e a postagem já está no forno!
Fique ligado!
Referência
- A parte 1 desta série é o Instagram Clone.
- A parte 2 é o Login do Instagram.
- A parte 3 é o Perfil do Instagram.
- A parte 4 é o Instagram HomeView.
- Veja o backend do aplicativo em iOS Instagram Clone Backend.
- Baixe um projeto iOS Instagram Clone com código-fonte e comece a usar o Back4App.
Inscreva-se agora no Back4App e comece a criar seu aplicativo clone do Instagram.