Клон Instagram с использованием SwiftUI и GraphQL – Вход
В нашей предыдущей заметке о том, как создать приложение-клон Instagram, вы узнали, как настроить все необходимое для работы SwiftUI в XCode 11, и создали полностью рабочее представление Sing Up с помощью GraphQL.
Сегодня мы узнаем, как создать представление для входа в систему и выхода пользователя из нее.
Нам понадобится проект из предыдущего поста, так что если вы не следили за ним, настоятельно рекомендую это сделать.
Пристегните ремни и поехали!
Для лучшего усвоения материала скачайте проект iOS Instagram Clone с исходным кодом.
Contents
Хотите быстро начать?
Клонируйте это приложение из нашего хаба и начните использовать его без лишних хлопот!
Создание представления входа в систему
Наше представление входа в систему будет очень похоже на представление регистрации, даже проще.
В мутации logInUser нам нужно только два параметра: имя пользователя и пароль:
Запросы и мутации будут зависеть от версии Parse, которую вы выбрали:
Parse 3.7.2:
мутация logInUser($username: String!, $password: String!){ пользователи{ logIn(имя пользователя: $username, пароль: $password){ sessionToken } } }
Parse 3.8:
мутация logInUser($username: String!, $password: String!){ logIn(имя пользователя: $username, пароль: $password){ sessionToken } }
Parse 3.9:
мутация logInUser($username: String!, $password: String!){ logIn(имя пользователя: $username, пароль: $password){ sessionToken } }
поэтому нам нужно будет задавать только эти параметры для наших пользователей.
Давайте начнем с добавления нового представления SwiftUI View, перейдя в меню Файл > Новый > Файл и выбрав наше представление SwiftUI View.
Назовем это представление LogInView.swift и добавим его в наш проект:
И, как вы уже узнали, создайте свой VStack с элементами управления, которые нам понадобятся:
- TextField для имени пользователя
- SecureField для пароля
- Кнопка для выполнения действия
Чтобы сохранить единообразие дизайна, я перенес цвета, которые мы будем использовать, в AppDelegate.swift, поэтому мне пришлось импортировать SwiftUI и туда:
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)
Не забудьте удалить цветовые линии из SignUpView.swift и LogInView.swift.
Также, чтобы сохранить согласованность элементов управления, я просто скопировал и вставил данные из нашего представления SignUp, удалил текстовое поле email и изменил текстовые поля, чтобы отразить новые функциональные возможности. В итоге мой код выглядел следующим образом:
struct LogInView: View { @State var username: 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("Имя пользователя", text: $username) .padding() .background(lightGreyColor) .cornerRadius(5.0) .padding(.bottom, 20) SecureField("Пароль", текст: $password) .padding() .background(lightGreyColor) .cornerRadius(5.0) .padding(.bottom, 20) Button(action: { }){ Text("Log In!") .font(.headline) .foregroundColor(.white) .padding() .frame(width: 220, height: 60) .background(lightBlueColor) .cornerRadius(15.0) } }.padding() } }
Это было просто. Давайте посмотрим, как это выглядит?
Измените свой ContentView.swift, чтобы показать это представление вместо него:
struct ContentView : View { var body: some View { LogInView() } }
Выглядит аккуратно!
Давайте сделаем его еще более аккуратным, добавив наш логотип в верхней части!
Наш логотип будет состоять из изображения, которое мы интегрируем в наш проект. Я использовал вот это.
Перетащите это изображение в папку Assets.xcassets в проекте:
Оно должно выглядеть так:
Теперь мы можем ссылаться на него в нашем коде с таким именем: logo-social.
Зарегистрируйтесь сейчас на Back4App и начните создавать свое приложение-клон Instagram.
Наш логотип
Наш логотип будет состоять из этого изображения, но просто поместить его туда будет выглядеть любительски. Мы сделаем его блестящим: круглым, с фиксированным размером, некоторыми штрихами по границам и, конечно же, drop shadow, потому что… drop shadow.
Код для всего этого выглядит следующим образом:
Image("logo-social") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 150, height: 150) .clipShape(Circle()) .overlay(Circle().stroke(Color.blue, lineWidth: 2)) .shadow(radius: 5) .padding(.bottom, 75)
И он помещается на вершину нашего VStack:
var body: some View { VStack{ Image("logo-social") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 150, height: 150) .clipShape(Circle()) .overlay(Circle().stroke(Color.blue, lineWidth: 2)) .shadow(radius: 5) .padding(.bottom, 75) Text("Log In") .font(.largeTitle) .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.bottom, 20) ...
Ну разве это не прекрасно?
Токен сессии
Наш процесс входа в систему будет возвращать строку sessionToken. Мы должны хранить этот sessionToken в безопасности, поскольку он будет использоваться во время наших операций: пока sessionToken действителен, мы будем иметь доступ к нашему приложению. Когда он будет удален или станет недействительным, наши вызовы будут отклонены.
Поскольку это напрямую связано с безопасностью, нам нужно хранить его в безопасном месте. Подходящим местом для этого является связка ключей на iOS.
Единственное, что можно сказать о связке ключей, – это то, что пользоваться ею сложно и скучно, поэтому я решил использовать эту обертку для управления ею. Это делает жизнь намного проще, и поскольку мы уже используем Cocoapods, это имеет полный смысл.
Давайте отредактируем наш Podfile и добавим это в наш pods:
pod 'SwiftKeychainWrapper'
Затем обновим наш Pods командой
pod install
и, наконец, снова откроем наш проект xcworkspace.
Теперь мы готовы использовать наш механизм Keychain, чтобы…
Хранить sessionToken
Согласно документации нашего нового подкада, способ сохранения значения в связке ключей следующий
KeychainWrapper.standard.set("SomeValue", forKey: "SomeKey")
но давайте добавим в него немного логики.
Для начала импортируем нашу обертку:
import SwiftKeychainWrapper
Первое, что нам нужно сделать, это вызвать мутацию logInUser, и когда она ответит, мы сохраним sessionToken, если он есть. Если нет, мы должны уведомить пользователя об этом.
Если вы помните, в предыдущей статье у нас уже было закодировано оповещение, включая его структуру. Давайте воспользуемся этим, удалив приведенный ниже код из нашего SingUpView.swift и передав его в наш файл AppDelegate.swift, чтобы все представления могли получить к нему доступ:
struct Message { var alertTitle: String = "" var alertText: String = "" } var myMessage = Message()
Теперь вернемся к нашей логике, первое, что мы должны сделать, это определить, заполнил ли пользователь текстовые поля имени пользователя и пароля. Если нет, то информации для входа в систему нет, и мы должны уведомить пользователя об этом.
Наш код действия для кнопки входа в систему должен это проверить. Давайте добавим в него этот фрагмент кода, который проверяет размер строки переменных состояния, связанных с этими текстовыми полями:
Button(action: { // Проверяем наличие введенного пароля if (self.password.count == 0 || self.username.count == 0){ // Если нет, мы должны показать предупреждение myMessage.alertText = "Вы должны указать имя пользователя и пароль". myMessage.alertTitle = "Упс..." self.showingAlert = true } else { // Если да, мы можем продолжить } }){ Text("Log In!") .font(.headline) .foregroundColor(.white) .padding() .frame(width: 220, height: 60) .background(lightBlueColor) .cornerRadius(15.0) } .alert(isPresented: $showingAlert) { Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText), dismissButton: .default(Text("OK"))) }
и протестируйте его…
Отлично!
Теперь мы должны вызвать нашу мутацию GraphQL, чтобы получить sessionToken и, если мы его получили, сохранить его в связке ключей.
Вы уже узнали, как вызывать мутации, так что давайте сделаем это, но на этот раз, если мы получим sessionToken, мы сохраним его:
// Выполняем мутацию LogInUser, передавая параметры, которые мы только что получили из наших текстовых полей apollo.perform(mutation: LogInUserMutation(username: self.username, password: self.password)){ result in // Давайте поменяем результат, чтобы отделить успешный результат от ошибки switch result { // В случае успеха case .success(let graphQLResult): // Пытаемся Parse наш результат if let sessionToken = graphQLResult.data?.users?.logIn.sessionToken { myMessage.alertTitle = "Ура!" myMessage.alertText = "Пользователь вошел в систему!" self.showingAlert = true print ("Токен пользователя " + sessionToken) // Записываем sessionToken в нашу связку ключей let _: Bool = KeychainWrapper.standard.set(sessionToken, forKey: "Back4Gram.sessionToken") } // но в случае каких-либо ошибок GraphQL мы выдаем это сообщение else if let errors = graphQLResult.errors { // ошибки GraphQL myMessage.alertTitle = "Oops!" myMessage.alertText = "У нас есть ошибка GraphQL: " + errors.description self.showingAlert = true print(errors) } // В случае неудачи мы выдаем это сообщение case .failure(let error): // Ошибки сети или формата ответа myMessage.alertTitle = "Oops!" myMessage.alertText = "У нас возникла ошибка: " + error.localizedDescription self.showingAlert = true print(error) } }
Давайте протестируем!
Отлично! Но как мы узнаем, что это действительно сработало?
Мы можем зайти в Parse Dashboard нашего приложения и проверить, записан ли там новый объект Session:
Как по маслу!
И раз уж мы здесь…
Как насчет того, чтобы добавить кнопку для выхода из системы? Просто для тестирования, чтобы мы знали, что все идет гладко:
Button(action: { // Проверяем, есть ли в хранилище sessionTokenСледует выходить только если вы вошли в систему. if (KeychainWrapper.standard.string(forKey: "Back4Gram.sessionToken") != nil) { print("Найден sessionToken! Мы можем выйти из системы"). // Выполняем мутацию LogOutUser apollo.perform(mutation: LogOutUserMutation()){ result in // Давайте поменяем результат, чтобы отделить успешный результат от ошибки switch result { // В случае успеха case .success(let graphQLResult): // Попробуем Parse наш результат if let result = graphQLResult.data?.users?.logOut { if (result) { myMessage.alertTitle = "Ура!" myMessage.alertText = "Пользователь вышел из системы!" self.showingAlert = true // Очистите хранящийся в памяти sessionToken let _: Bool = KeychainWrapper.standard.set("", forKey: "Back4Gram.sessionToken") } else { myMessage.alertTitle = "Oops!" myMessage.alertText = "Операция выхода пользователя из системы вернула False." self.showingAlert = true } } // но в случае каких-либо ошибок GraphQL мы выдаем это сообщение else if let errors = graphQLResult.errors { // ошибки GraphQL myMessage.alertTitle = "Oops!" myMessage.alertText = "У нас есть ошибка GraphQL: " + errors.description self.showingAlert = true print(errors) } // В случае неудачи мы выдаем это сообщение case .failure(let error): // Ошибки сети или формата ответа myMessage.alertTitle = "Oops!" myMessage.alertText = "У нас возникла ошибка: " + error.localizedDescription self.showingAlert = true print(error) } } } else { // Ошибки сети или формата ответа myMessage.alertTitle = "Упс!" myMessage.alertText = "Похоже, пользователь не вошел в систему". self.showingAlert = true } }){ Text("Log Out") .font(.headline) .foregroundColor(.white) .padding() .frame(width: 220, height: 60) .background(lightBlueColor) .cornerRadius(15.0) } .alert(isPresented: $showingAlert) { Alert(title: Text(myMessage.alertTitle), message: Text(myMessage.alertText), dismissButton: .default(Text("OK"))) }
И снова давайте протестируем!
Отлично!
Мы перенесем эту кнопку выхода из системы в другое место позже, но пока она работает для того, чтобы показать, что наш поток работает.
Но как насчет нашего объекта Session теперь, когда мы вышли из системы?
Он автоматически исчез, как и ожидалось!
Заключение
Поздравляем! Теперь у вас есть функции входа и выхода из системы! Мало того, вы научились вызывать различные мутации, проверять результаты и сохранять значения в связке ключей! Как же это здорово!
В следующей главе мы начнем работать с несколькими представлениями и начнем строить наше главное представление!
Следите за новостями!
Ссылка
- Первая часть этой серии – Instagram Clone с использованием Swift UI и GraphQL.
- Часть 2 – Вход в Instagram с помощью Swift UI и GraphQL.
- Часть 3 – Просмотр профиля с помощью Swift UI и GraphQL.
- Часть 4 – Instagram Clone Home View.
- Загрузите проект iOS Instagram Clone с исходным кодом и начните использовать Back4App.
Зарегистрируйтесь сейчас в Back4App и начните создавать свое приложение Instagram Clone.
Что такое SwiftUI?
SwiftUI — это новый способ создания пользовательских интерфейсов для приложений на платформах Apple. Он позволяет разработчикам определять UI с помощью кода Swift.
Что такое sessionToken?
Процесс входа, который мы разрабатываем, вернет строку sessionToken. Он должен быть защищен. Если мы сможем сохранить sessionToken действительным, у нас будет доступ к приложению, в противном случае мы потеряем доступ к приложению. Это связано с безопасностью.
Что такое брелок?
Мы знаем, что sessionToken связан с безопасностью вашего приложения. Поэтому его нужно хранить в безопасном месте. Это безопасное место называется keychain. Его может быть немного сложно использовать, и он также может показаться скучным.