Клон Instagram с использованием SwiftUI и GraphQL
Сегодня мы начинаем серию постов в блоге, которые научат вас использовать множество крутых инструментов для создания собственной социальной сети – приложения, напоминающего Instagram.
Мы не будем экономить на технологиях и будем использовать все самое новое и лучшее: Parse, GraphQL, немного NodeJS и особенно (еще не вышедший) новейший фреймворк Apple SwiftUI.
Это займет несколько постов, чтобы стать полностью функциональным, но когда мы доберемся до этого, вы поймете, насколько просто можно воплотить свои идеи в жизнь с минимальными усилиями здесь, в Back4app.
Для лучшего усвоения материала скачайте проект iOS Instagram Clone с исходным кодом.
Итак, кажется, настало время для…
Contents
Хотите быстрый старт?
Клонируйте это приложение из нашего хаба и начните использовать его без лишних хлопот!
Немного вступления
Ну, вы, наверное, уже знаете, какие огромные преимущества приносит Parse в ваш процесс разработки, особенно если он размещен на Back4app, поскольку наш набор инструментов повышает производительность на огромную величину.
А наши последние посты о GraphQL показали вам, насколько легко поддерживать развитие API со временем, если вы используете его. Если вы пропустили эти посты, я настоятельно рекомендую вам найти время и прочитать их, так как они значительно улучшат ваше понимание по ходу дела.
Вы можете прочитать больше о GraphQL, а также у нас есть полный пост о том, как использовать его в статье Web API.
Вы также можете увидеть некоторые интеграции с NodeJS в Cloud Code Functions, прочитав GraphQL и NodeJS.
Остается SwiftUI.
SwiftUI – это, по словам Apple, “инновационный, исключительно простой способ создания пользовательских интерфейсов на всех платформах Apple с помощью возможностей Swift”.
Мне самому нравится думать, что SwiftUI – это нечто большее.
Если вы, как и я, разрабатываете программное обеспечение в течение некоторого времени, вы, вероятно, знаете, что сообщество разработчиков всегда разделялось между удобством использования Storyboards, интерфейса Apple для создания пользовательских интерфейсов методом перетаскивания, и мощью программных пользовательских интерфейсов.
SwiftUI предлагает лучшее из двух миров: он достаточно прост для освоения новичками, но при этом сохраняет удобство сопровождения кодированного пользовательского интерфейса.
Соедините это с рендерингом в реальном времени, чтобы разработчики могли легко видеть визуальный результат того, что они кодируют, и сделайте его двусторонним, чтобы при изменении кода он отражался на пользовательском интерфейсе, а при графическом изменении пользовательского интерфейса он отражался на коде, и вы получите один из самых мощных, но простых способов создания красиво оформленных, но при этом поддерживаемых в течение долгого времени пользовательских интерфейсов.
Итак, что же вам понадобится?
На момент написания этой заметки Apple все еще проводит бета-тестирование новой macOS Catalina, XCode 11, iOS 13 и еще нескольких продуктов, которые должны скоро появиться на полках магазинов.
Хотя вы можете использовать XCode 11 с macOS Mojave, вам понадобится Catalina для рендеринга превью SwiftUI в режиме реального времени.
Нет Catalina? Никаких предварительных просмотров для вас!
Это решение вы должны принять сами, так как я обнаружил, что переход с Bash на ZSH в качестве официальной оболочки macOS в Catalina сломал многие мои инструменты и скрипты, которые я использую для ежедневного рабочего процесса, и я решил остаться в Mojave на некоторое время. Вам решать, обновляться или нет.
Кроме того, вам понадобится учетная запись Back4app с созданным приложением. О том, как создать свое первое приложение, вы можете узнать из документации Back4app.
И последнее, но не менее важное: мы будем использовать Apollo Client, чтобы интегрировать GraphQL в наш проект. Если у вас нет опыта работы с ним, я написал очень подробный пост о том, как настроить и использовать веб-аутентификацию.
Итак, прежде чем мы начнем, вам понадобятся:
- Установленный XCode 11 (с macOS Catalina, если вы хотите посмотреть предварительные версии)
- Ваше новое приложение, созданное в Back4app
- Установленный и настроенный клиент Apollo.
Мое приложение будет называться Back4Gram, так как оно будет напоминать Instagram.
Наш самый первый класс
Я всегда рассказываю вам о том, как Parse создает класс User, чтобы вы могли регистрировать новых пользователей и аутентифицировать существующих, и класс Role, чтобы вы могли группировать пользователей с одинаковыми привилегиями. Это здорово, потому что, по крайней мере для аутентификации, нам не нужно создавать никаких классов.
Класс User имеет три автоматически создаваемых свойства, два из которых обязательны и одно необязательно:
- имя пользователя (обязательно)
- пароль (обязательно)
- электронная почта (необязательно)
Этого достаточно, чтобы наше приложение могло регистрировать пользователей и управлять ими.
Как вы, вероятно, знаете, политика Apple в отношении хранения электронной почты пользователей очень строга, поэтому я не рекомендую запрашивать или хранить эту информацию, иначе вы можете получить неодобрение.
Поскольку это только учебник, я попрошу пользователя ввести эту информацию, просто чтобы вы могли увидеть, как это работает.
Классы пользователей и ролей создаются автоматически
Как будто это было недостаточно полезно, ваше только что созданное приложение уже имеет автоматически созданные для нас GraphQL-запросы и мутации. Это относится как к нашим автоматически созданным классам, так и к любым другим классам, которые вы создадите со временем. Мы также позаботились о документации!
Я говорил, что у нас есть автозаполнение? Да!
Итак, моя первая мутация будет такой, как описано выше, и будет использовать нашу специфическую мутацию signUp() для создания нового пользователя:
Запрос будет зависеть от версии Parse, которую вы используете:
Parse 3.7.2:
мутация signUpUser($username: String!, $password: String!, $email: String) { пользователи { signUp( поля: { имя пользователя: $username, пароль: $password, email: $email } ) { objectId } } }
Parse 3.8:
мутация signUpUser($username: String!, $password: String!, $email: String) { signUp( поля: { имя пользователя: $username, пароль: $password, email: $email } ) { objectId } }
Parse 3.9:
мутация signUpUser($username: String!, $password: String!, $email: String) { signUp( поля: { имя пользователя: $username, пароль: $password, email: $email } ) { id } }
Обратите внимание, что я делаю переменные username и password обязательными, ставя “!” в конце их типов, в то время как email является необязательным, не имея “!” после своего типа.
В итоге я возвращаю objectId только что созданного пользователя.
И раз уж мы здесь, давайте также разработаем нашу мутацию GraphQL для входа в уже существующего пользователя.
В этом случае имя пользователя и пароль являются обязательными, и я буду получать sessionToken для пользователя:
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 } }
И, наконец, мутация для выхода пользователя из системы, которая не требует никаких параметров и возвращает только булево значение:
Parse 3.7.2
мутация logOutUser{ пользователи{ logOut } }
Parse 3.8
мутация logOutUser{ logOut }
Parse 3.9
мутация logOutUser{ logOut }
Добавьте все эти файлы в новый файл UserMutations.graphql, а также сохраните файл schema.graphql, который вы узнали, как загрузить в нашей предыдущей статье.
Сохраните эти файлы, так как мы будем добавлять их в наш…
Проект SwiftUI
Давайте создадим наш проект SwiftUI.
Запустите XCode 11 и нажмите кнопку “Создать новый проект XCode” в окне “Добро пожаловать в XCode”.
Если у вас не открывается это окно, вы всегда можете перейти в меню Файл > Новый > Проект.
Выберите iOS App в верхней части, затем Single View App. Дайте ему хорошее название и не забудьте выбрать Swift в качестве языка и SwiftUI в качестве пользовательского интерфейса:
Не забудьте выбрать SwiftUI в качестве пользовательского интерфейса.
Теперь установите Apollo Client, как вы узнали из нашей статьи о веб-апи, и после этого добавьте файлы UserMutations.graphql и schema.graphql в ваш проект.
Скомпилируйте и добавьте в проект только что созданный файл API.swift.
На данном этапе я ожидаю, что у вас будет следующее:
- установленный и запущенный XCode 11
- Клиент Apollo установлен и интегрирован в ваш проект XCode
- Файлы UserMutations.graphql и schema.graphql добавлены в ваш проект XCode
- Сгенерированный файл API.swift добавлен в ваш проект XCode
Ваш проект должен выглядеть примерно так:
Все интегрировано в XCode 11
Теперь мы готовы приступить к работе с SwiftUI.
Зарегистрируйтесь в Back4App и начните создавать свое приложение-клон Instagram.
Выбор элементов управления
Прежде чем мы сможем войти или выйти из пользователя, мы должны сначала создать этого пользователя. Поэтому сначала мы создадим экран регистрации.
Обычно на этом экране может быть от 4 до 5 элементов управления:
- Какой-нибудь текст, чтобы сообщить пользователю, что это за экран (текст)
- Имя пользователя (элемент управления для ввода текста)
- Пароль (безопасный элемент управления для ввода текста)
- Подтверждение пароля (необязательно, зависит от платформы, безопасный элемент управления вводом текста)
- Электронная почта (форматированный элемент управления вводом текста)
- Кнопка “Зарегистрироваться” (кнопка)
Поскольку это будет мобильное приложение, я решил не размещать элемент управления для ввода текста “Подтверждение пароля”, так как большинство мобильных платформ позволяют пользователю видеть последний набранный символ, и в большинстве случаев этого достаточно, чтобы определить, правильно ли набран пароль.
Эти элементы управления будут отображаться вертикально выровненными, один под другим, в порядке, указанном выше, и каждый из них будет иметь свой цвет.
Собственно создание пользовательского интерфейса
Как я уже говорил, я не использую macOS Catalina, поэтому у меня не будет включена функция предварительного просмотра, но я скомпилирую и покажу вам результаты вместе с моим кодом в том виде, в котором он был.
Создайте новый SwiftUI View, перейдя в меню File > New > File и выбрав SwiftUI View в разделе User Interface.
Назовите этот файл SignUpView.swift, и он появится интегрированным в ваш проект.
Вы заметите, что в только что созданном представлении уже есть элемент управления Text со строкой “Hello World!”.
Прелесть Swift в том, что вы можете создавать множество простых представлений, содержащих только необходимые для этого представления элементы управления, а затем объединять несколько представлений в более сложное. Это объектная ориентация в лучшем ее проявлении.
Мы решили выровнять наши элементы управления по вертикали, и для этого в SwiftUI есть специальный элемент управления под названием VStack (Vertical Stack): все, что находится внутри VStack, будет автоматически укладываться по вертикали, поэтому для проверки добавим элемент управления VStack в наш ContentView.swift и внутри него дважды добавим SignUpView:
Добавили SignUpView дважды в VStack: он отображается дважды вертикально выровненным
Как же это здорово! Если нам понадобится больше элементов управления, мы можем просто продолжать добавлять их!
Давайте уберем VStack и дублирование и будем иметь только один SignUpView() в этом View, а затем вернемся к нашему SignUpView и начнем его изменять, поскольку мы уже видели результаты нашего теста. Теперь ваш ContentView должен выглядеть примерно так:
struct ContentView: View { var body: some View { SignUpView() } }
Мы уже знаем, что нам понадобится VStack с Text в верхней части экрана, поэтому давайте просто добавим VStack в SignUpView и изменим строку “Hello World!” на что-нибудь полезное, например “Sign Up”. Наш SignUpView должен выглядеть следующим образом:
struct SignUpView: View { var body: some View { VStack{ Text("Sign Up") } } }
И поскольку мы уже знаем, какие элементы управления нужно добавить ниже, мы можем добавить:
- a TextField для имени пользователя
- поле SecureField для пароля
- текстовое поле для электронной почты
- кнопку для регистрации.
TextFields и SecureField очень похожи на старый UITextField, но должны полагаться на привязку к состоянию, поэтому мы объявим их как:
Control(“Placeholder”, text: stateValueToBindTo)
и самый первый шаг – объявить состояние, к которому нужно привязаться:
@State var username: String = "" @State var password: String = "" @State var email: String = ""
После этого мы можем начать добавлять наши элементы управления:
Text("Sign Up") TextField("Имя пользователя", text: $username) SecureField("Пароль", текст: $password) TextField("Email (необязательно)", text: $email)
И последнее, но не менее важное, мы можем добавить нашу кнопку, которая имеет такую структуру:
Button(action: {
//Код для выполнения при срабатывании
}){
//Содержание
}
Как вы, наверное, заметили, он работает так же, как и UIButton, но его структура гораздо более гибкая, поскольку мы можем программно определять его содержимое, добавляя туда практически все, что угодно: текст, изображение. Да что угодно.
Старая структура target/action исчезла, ее заменила новая структура action:{} для обработки поведения при нажатии.
В нашей кнопке будет текст “Зарегистрироваться!”, так что давайте добавим его:
Button(action: { }){ Text("Sign Up!") }
И ваш финальный код должен выглядеть следующим образом:
struct SignUpView: View { @State var username: String = "" @State var password: String = "" @State var email: String = "" var body: some View { VStack{ Text("Sign Up") TextField("Имя пользователя", text: $username) SecureField("Пароль", текст: $password) TextField("Email (необязательно)", text: $email) Button(action: { }){ Text("Sign Up!") } } } }
А как вам наш предварительный просмотр?
Выглядит многообещающе, но мы можем многое улучшить, задав несколько визуальных свойств.
И если в 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)
Затем давайте добавим несколько приятных особенностей:
Немного отступов, чтобы у нас было пространство между этими элементами.
Установите свойства background и foregroundColor, чтобы наши элементы управления соответствовали нашему бренду.
Задайте уголRadius, чтобы у нас были закругленные углы.
И установите шрифт и fontWeight, чтобы сделать все красивее.
Вуаля!
Красиво или как?
Просто чтобы вы могли следить за нашим кодом на этом этапе:
struct SignUpView: View { @State var username: 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(красный: 36.0/255.0, зеленый: 158.0/255.0, синий: 235.0/255.0, непрозрачность: 1.0) var body: some View { VStack{ Text("Sign Up") .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) TextField("Email (необязательно)", text: $email) .padding() .background(lightGreyColor) .cornerRadius(5.0) .padding(.bottom, 20) Button(action: { }){ Text("Sign Up!") .font(.headline) .foregroundColor(.white) .padding() .frame(width: 220, height: 60) .background(lightBlueColor) .cornerRadius(15.0) } }.padding() } }
Итак, у нас почти готов наш красивый блестящий экран.
Как насчет…
Немного функциональности
Теперь, когда мы получили готовый пользовательский интерфейс, пришло время добавить вызовы Apollo в наши методы GraphQL.
Поскольку мы собираемся использовать эти вызовы почти во всех представлениях, нам следует создать клиента Apollo в таком месте, чтобы все представления могли получить к нему доступ. Таким местом является AppDelegate.swift.
Объявите в нем свой клиент Apollo ниже строки
import UIKit
и началом блока кода
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { ...
как мы показывали в предыдущей статье, и теперь все Views могут выполнять GraphQL-запросы и мутации.
Сначала нужно импортировать фреймворк Apollo, а затем инстанцировать клиента следующим образом:
import Apollo // Инициализация клиента Apollo. // Подробнее об этом здесь: 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, конфигурация: конфигурация ) ) }()
Просто замените строки YourAppIdHere и YourClientKeyHere на ваши текущие значения, и мы сможем продолжить.
Как вы, наверное, догадались, мы должны выполнить мутацию для SignUp при нажатии на кнопку Sign Up, поэтому в блоке действий вызываем ее:
// Выполнить мутацию SignUpUser, передав параметры, которые мы только что получили из наших TextFields apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in // Давайте поменяем результат, чтобы отделить успешный результат от ошибки switch result { // В случае успеха case .success(let graphQLResult): // Пытаемся Parse наш результат if let objId = graphQLResult.data?.users?.signUp.objectId { print ("Пользователь создан с ObjectId: " + objId) } // но в случае каких-либо ошибок GraphQL мы выдаем это сообщение else if let errors = graphQLResult.errors { // ошибки GraphQL print(errors) } // В случае неудачи мы представляем это сообщение case .failure(let error): // Ошибки сети или формата ответа print(error) } }
Это будет работать, но эти методы print() будут выводить данные только в консоль. Нам нужно представить пользователю оповещение, поэтому давайте изменим его на Alert со следующей структурой:
Alert(title: Text(“Заголовок”), message: Text(“Message”), dismissButton: .default(Text(“TextOfButton”))
Поскольку нам понадобится изменить заголовок и сообщение во время выполнения, мы должны задать Struct для обработки этих значений, так как переменные self не могут быть изменены во время выполнения:
struct Message { var alertTitle: String = "" var alertText: String = "" } var myMessage = Message()
А также создайте состояние, чтобы представление могло знать, когда показывать оповещение:
@State private var showingAlert = false
И наш полный код для действия кнопки должен выглядеть следующим образом:
Button(action: { // Выполняем мутацию SignUpUser, передавая параметры, которые мы только что получили из наших TextFields apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in // Давайте поменяем результат, чтобы отделить успешный результат от ошибки switch result { // В случае успеха case .success(let graphQLResult): // Пытаемся Parse наш результат if let objId = graphQLResult.data?.users?.signUp.objectId { myMessage.alertTitle = "Ура!" myMessage.alertText = "Пользователь зарегистрировался!" self.showingAlert = true print ("Пользователь создан с ObjectId: " + objId) } // но в случае каких-либо ошибок 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) } } }){ Text("Sign Up!") .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"))) }
Выглядит многообещающе, да?
Итак, настало время.
Компилируем и запускаем. Попробуйте зарегистрировать нового пользователя, и вы должны получить…
Давайте посмотрим, создан ли пользователь в Parse Dashboard!
Заключение
Вы сделали полностью рабочее представление SwiftUI, которое выполняет GraphQL-мутации с помощью Apollo Client. Как же это здорово!
Мы продолжим работу над нашим приложением-клоном Instagram! Следующие шаги – вход и выход, а пост уже в духовке!
Оставайтесь с нами!
Ссылка
- Часть 1 этой серии – Клон Instagram.
- Часть 2 – Вход в Instagram.
- Часть 3 – Профиль Instagram.
- Часть 4 – Instagram HomeView.
- Посмотреть бэкенд приложения можно на iOS Instagram Clone Backend.
- Загрузите проект iOS Instagram Clone с исходным кодом и начните использовать Back4App.
Зарегистрируйтесь сейчас в Back4App и начните создавать свое приложение Instagram Clone.