Клон Instagram с использованием SwiftUI и GraphQL – ProfileView
Сегодня в третьей части нашего приложения-клона Instagram с GraphQL мы углубимся в SwiftUI, создавая представление профиля.
Мы научимся повторно использовать структуры в SwiftUI и обсудим несколько элементов управления: VStack, HStack, GeometryReader, Text, Button, Spacer, Image, Divider и другие.
В наших предыдущих статьях мы узнали , как зарегистрировать пользователя и как войти в него, с соответствующими пользовательскими интерфейсами в нашем приложении-клоне Instagram. Сегодня мы сделаем это намного красивее.
Пристегните ремни и давайте начнем!
Для лучшего усвоения материала скачайте проект iOS Instagram Clone с исходным кодом.
Contents
- 1 Хотите быстро начать?
- 2 Что именно мы будем создавать
- 3 Что такое HStack, Vstack, Zstack?
- 4 Возможность многократного использования
- 5 Создание представления профиля
- 6 Второй HStack
- 7 Добавление кнопки редактирования профиля и разделителя
- 8 Издевательство над нашей временной шкалой
- 9 Вид снизу
- 10 И наконец…
- 11 Заключение
- 12 Ссылка
- 13 В чем преимущество SwiftUI?
- 14 Что такое Hstack, Vstack и Zstack?
- 15 Какая лучшая функция SwiftUI?
Хотите быстро начать?
Клонируйте это приложение из нашего хаба и начните использовать его без лишних хлопот!
Что именно мы будем создавать
Мы будем строить следующее представление:
SwiftUI позволяет очень легко создавать такие сложные интерфейсы, поскольку вы можете повторно использовать множество компонентов очень легко и просто.
Единственное, мы должны научиться создавать эти многократно используемые компоненты. На самом деле одно представление – это комбинация нескольких представлений, которые объединяются в один конечный результат.
Упрощенный список компонентов выглядит следующим образом:
Где:
- VStack
- Hstack
- Собственно элементы управления (кнопки, разделители и т. д.)
Что такое HStack, Vstack, Zstack?
SwiftUI строит свои пользовательские интерфейсы, выравнивая представления по вертикали с помощью VStack (Vertical Stack) и по горизонтали с помощью HStack (Horizontal Stack) и накладывая представления с помощью Zstack (Z – это z-index).
Каждый раз, когда вам нужен один вид под другим, вы должны использовать VStack.
Каждый раз, когда вам нужен один вид рядом с другим, вы должны использовать HStack.
Каждый раз, когда нужно, чтобы один вид накладывался на другой, следует использовать ZStack.
Поскольку наш основной вид будет состоять из множества видов, расположенных горизонтально один под другим, мы поместим все в основной VStack и начнем с него, но есть еще один, который нам нужно будет использовать, чтобы занять весь экран: GeometryReader.
Он позволяет нам рендерить все как функцию от его (GeometryReader’а) размера и координат.
Возможность многократного использования
Возможность повторного использования – одна из прелестей SwiftUI, так что мы можем создавать сложные элементы управления и повторно использовать их в любом месте, где это необходимо.
В этом приложении есть два компонента, которые мы будем повторно использовать в различных представлениях: временная шкала и нижняя панель:
По этой причине мы создадим эти UI-коды отдельно, чтобы все было более организованно. Весь остальной код пользовательского интерфейса, специфичный для представления, может оставаться в файле кода этого представления.
Итак, давайте начнем…
Создание представления профиля
Добавьте новый файл SwiftUI и назовите его ProfileView.swift.
Поскольку мы будем выравнивать компоненты, чтобы заполнить экран, давайте начнем добавлять GeometryReader, чтобы убедиться, что все элементы управления внутри него будут использовать его размеры и координаты:
struct ProfileView: View { var body: some View { GeometryReader { geometry in } } }
Теперь наше представление будет встроено в основной VStack, который будет содержать все наши элементы управления, поэтому давайте добавим и его
struct ProfileView: View { var body: some View { GeometryReader { геометрия в VStack{ } } } }
И наша самая первая “строка” элементов управления будет верхней:
То, что выглядит как простая строка элементов управления, на самом деле является несколькими элементами управления:
- текст “имя пользователя”
- маленькая стрелка, указывающая вниз, рядом с ним
- пробел, заполняющий пространство между этой стрелкой и следующим элементом управления
- кнопка “Гамбургер” справа.
Мы будем использовать иконки для кнопок, поэтому не забудьте найти несколько бесплатных иконок. Хорошим местом для их поиска является Freepik.
Итак, давайте добавим наш HStack:
struct ProfileView: View { var body: some View { GeometryReader { геометрия в VStack{ HStack{ } } } } }
А в нем – наш первый текст:
struct ProfileView: View { var body: some View { GeometryReader { геометрия в VStack{ HStack{ Text("имя пользователя") } } } } }
Итак, вы, вероятно, пришли к этому:
Хорошо. Начало положено.
Давайте выровняем его по левому краю, добавив
(выравнивание: .leading)
в наш VStack:
struct ProfileView: View { var body: some View { GeometryReader { геометрия в VStack(alignment: .leading){ HStack{ Text("имя пользователя") } } } } }
Мы также можем добавить некоторые свойства Text, чтобы сделать его более соответствующим нашему дизайну:
Чтобы установить его цвет на наш lightBlueColor, который определен в нашем AppDelegate:
.foregroundColor(lightBlueColor)
Чтобы изменить вес шрифта:
.fontWeight(.semibold)
И, наконец, добавьте немного подложки (пробела) в ведущей части:
.padding(.leading, 10)
Теперь ваш код должен выглядеть следующим образом:
struct ProfileView: View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) } } } } }
И ваш пользовательский интерфейс должен выглядеть так:
Теперь давайте добавим маленькую кнопку со стрелкой.
Как вы узнали во второй части этой статьи, добавьте изображение стрелки вниз для нашей кнопки, и давайте добавим кнопку в наш код SwiftUI:
Button(action: {}){ }
И добавим в нее наше изображение:
Button(action: {}){ Image("arrow-down") .resizable() .frame(width: 10, height: 10) }
И просто для выравнивания, давайте добавим немного подкладки сверху:
Button(action: {}){ Image("arrow-down") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5)
Теперь наш полный код должен выглядеть следующим образом:
struct ProfileView: View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) Кнопка(действие: {}){ Изображение("стрелка вниз") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) } } } } }
И вот наш пользовательский интерфейс:
Теперь давайте добавим кнопку гамбургера тем же способом:
Button(action: {}){ Image("menu") .resizable() .frame(width: 20, height: 20) }.padding()
Наш полный код:
struct ProfileView: View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) Кнопка(действие: {}){ Изображение("стрелка вниз") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) Button(action: {}){ Изображение("меню") .resizable() .frame(width: 20, height: 20) }.padding() } } } } }
И вот наш вид:
Если бы только было что-то, что мы могли бы поместить между этими двумя кнопками, чтобы занять все пространство между ними и выровнять все…
Spacer()
Теперь все выглядит хорошо!
Наш полный код на данный момент:
struct ProfileView: View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) Кнопка(действие: {}){ Изображение("стрелка вниз") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) Spacer() Кнопка(действие: {}){ Изображение("меню") .resizable() .frame(width: 20, height: 20) }.padding() } } } } }
А теперь давайте просто зафиксируем высоту этого HStack и дадим ему немного отступов по ведущей стороне, и все готово:
.frame(height: 50) .padding(.leading, 10)
Полный код:
struct ProfileView: View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) Кнопка(действие: {}){ Изображение("стрелка вниз") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) Spacer() Кнопка(действие: {}){ Изображение("меню") .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) } } } }
Теперь мы можем начать нашу…
Второй HStack
Поскольку мы создали все внутри основного VStack, каждый новый элемент управления, который мы добавим за пределы этого первого HStack, будет автоматически помещен под него.
Поэтому пришло время сделать второй HStack и построить вторую часть нашего экрана:
Это также будет HStack, содержащий внутри 4 VStack: один для изображения и текста под ним, и 3 для чисел с текстом под ними.
Поскольку вы, вероятно, уже поняли концепцию, я приведу полный код этого нового HStack:
HStack{ VStack{ Image("logo-social") .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) Text("Your Name") .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Публикации") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ Text("100") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Последователи") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ Text("1000") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10)
И наш полный код будет выглядеть так:
struct ProfileView: View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) Кнопка(действие: {}){ Изображение("стрелка вниз") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) Spacer() Кнопка(действие: {}){ Изображение("меню") .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) HStack{ VStack{ Image("logo-social") .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) Text("Your Name") .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Публикации") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ Text("100") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Последователи") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ Text("1000") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10) } } } }
Таким образом, наше представление будет выглядеть так:
Отлично!
Добавление кнопки редактирования профиля и разделителя
Можно подумать, что кнопка редактирования профиля и разделитель под ней должны находиться в VStack:
Но в этом нет необходимости, так как все наше представление находится внутри нашего основного VStack, поэтому мы можем просто добавить их в наш код:
Button(action: {}){ Text("Edit Profile") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() Разделитель()
что будет выглядеть следующим образом:
struct ProfileView: View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) Кнопка(действие: {}){ Изображение("стрелка вниз") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) Spacer() Кнопка(действие: {}){ Изображение("меню") .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) HStack{ VStack{ Image("logo-social") .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) Text("Your Name") .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Публикации") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ Text("100") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Последователи") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ Text("1000") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10) Кнопка(действие: {}){ Text("Редактировать профиль") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() Divider() } } } }
и наш пользовательский интерфейс:
Издевательство над нашей временной шкалой
Наше представление временной шкалы будет использоваться в других частях приложения, поэтому имеет смысл разбить его в отдельный файл.
Его можно так же повторно использовать из ProfileView, но это позволит лучше организовать работу при разделении кода.
Создайте файл TimelineView.swift.
В SwiftUI существует множество различных способов отображения данных, но для своего приложения я выбрал именно этот:
- Наше представление Timeline – это VStack из LineViews.
- Каждый LineView представляет собой HStack, состоящий из 3 PreviewViews
- В каждом PreviewView есть изображение
Первое, что я сделаю, это создам структуру для хранения наших данных. Я назову эту структуру Preview, и она будет иметь 2 параметра: id (тип Int) для итерации и imageURL (тип String), который будет содержать URL для изображения, которое я буду передавать:
struct Preview { var id: Int let imageUrl: String }
Как я уже говорил, вы можете выбрать другой способ отображения данных, но я нашел этот очень простым для понимания, поэтому давайте сначала добавим структуру для нашего PreviewView.
В нашей структуре есть свойство Preview, которое я задам позже, но для вывода изображения мы будем использовать свойство imageURL:
struct PreviewView: View { let preview: Preview var body: some View { Image(preview.imageUrl) .resizable() .frame(width: 136, height: 136) } }
После этого мы можем добавить структуру для нашего LineView, которая получает массив из 3 превью для отображения в строке.
В будущем я изменю эту структуру, чтобы она отражала реальные данные, но пока все в порядке:
struct LineView: View { let previewArray:[Preview] var body: some View { HStack(spacing: 2){ PreviewView(preview: previewArray[0]) PreviewView(preview: previewArray[1]) PreviewView(preview: previewArray[2]) } } }
Наконец, мы можем создать массив объектов Preview, по которым мы будем работать в цикле:
let previews:[Preview] = [ Preview(id: 0, imageUrl: "1"), Preview(id: 1, imageUrl: "2"), Preview(id: 2, imageUrl: "3"), Preview(id: 3, imageUrl: "4"), Preview(id: 4, imageUrl: "5"), Preview(id: 5, imageUrl: "6"), Preview(id: 6, imageUrl: "7"), Preview(id: 7, imageUrl: "8"), Preview(id: 8, imageUrl: "9"), Preview(id: 9, imageUrl: "10"), Preview(id: 10, imageUrl: "11"), Preview(id: 11, imageUrl: "12"), Preview(id: 12, imageUrl: "13") ]
В этом массиве 13 объектов, и я сослался на изображения, которые буду использовать, с именами от 1 до 13. Я также сохранил эти изображения в папке Assets, но, опять же, в будущем я это изменю:
Теперь, когда все готово, мы можем пройтись по нашему массиву и создать LinePreviews, передав ему 3 объекта предварительного просмотра.
Заметьте, что я передаю один и тот же объект, но, опять же, это временно для отображения и будет изменено:
var body: some View { ScrollView{ VStack(alignment: .leading, spacing: 2){ ForEach(previews, id: \.id) { preview in LineView(previewArray: [preview, preview, preview]) } } } }
Таким образом, наш полный код будет выглядеть примерно так:
struct TimelineView: View { let previews:[Preview] = [ Preview(id: 0, imageUrl: "1"), Preview(id: 1, imageUrl: "2"), Preview(id: 2, imageUrl: "3"), Preview(id: 3, imageUrl: "4"), Preview(id: 4, imageUrl: "5"), Preview(id: 5, imageUrl: "6"), Preview(id: 6, imageUrl: "7"), Preview(id: 7, imageUrl: "8"), Preview(id: 8, imageUrl: "9"), Preview(id: 9, imageUrl: "10"), Preview(id: 10, imageUrl: "11"), Preview(id: 11, imageUrl: "12"), Preview(id: 12, imageUrl: "13") ] var body: some View { ScrollView{ VStack(alignment: .leading, spacing: 2){ ForEach(previews, id: \.id) { preview in LineView(previewArray: [preview, preview, preview]) } } } } } struct TimelineView_Previews: PreviewProvider { static var previews: some View { TimelineView() } } struct Preview { var id: Int var imageUrl: String } struct LineView: View { let previewArray:[Preview] var body: some View { HStack(spacing: 2){ PreviewView(preview: previewArray[0]) PreviewView(preview: previewArray[1]) PreviewView(preview: previewArray[2]) } } } struct PreviewView: View { let preview: Preview var body: some View { Image(preview.imageUrl) .resizable() .frame(width: 136, height: 136) } }
И если мы вызовем его из нашего ProfileView.swift прямо под нашим разделителем:
... Button(action: {}){ Text("Edit Profile") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() Разделитель() TimelineView().padding(.leading, 10) ...
Мы также можем добавить еще один разделитель прямо под ним, чтобы получить почти окончательный результат, который мы хотим:
... Button(action: {}){ Text("Edit Profile") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() Разделитель() TimelineView().padding(.leading, 10) Разделитель() ...
Как это выглядит?
Уже неплохо?
Давайте закончим его, добавив наши…
Вид снизу
Нижний вид будет еще одним файлом, так как мы будем использовать его в нескольких частях приложения.
Создайте файл BottomView.swift и в нем создайте HStack (так как кнопки будут располагаться рядом) из 4 кнопок с разделителями между ними. Не забудьте про иконки!
struct BottomView: View { var body: some View { HStack{ Button(action: {}){ Image("home") .resizable() .frame(width: 30, height: 30) }.padding() Spacer() Кнопка(действие: {}){ Изображение("поиск") .resizable() .frame(width: 30, height: 30) }.padding() Spacer() Кнопка(действие: {}){ Image("plus-button") .resizable() .frame(width: 30, height: 30) }.padding() Spacer() Кнопка(действие: {}){ Изображение("сердце") .resizable() .frame(width: 30, height: 30) }.padding() Spacer() Кнопка(действие: {}){ Изображение("пользователь") .resizable() .frame(width: 30, height: 30) }.padding() }.frame(height: 35) } }
Это было просто! Давайте интегрируем его в ProfileView.swift, прямо под нашим последним разделителем:
... Divider() TimelineView().padding(.leading, 10) Разделитель() BottomView() ...
Таким образом, наш полный код ProfileView будет выглядеть так:
import SwiftUI struct ProfileView: View { var body: some View { GeometryReader { геометрия в VStack(alignment: .leading){ HStack{ Text("username") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) Кнопка(действие: {}){ Изображение("стрелка вниз") .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) Spacer() Кнопка(действие: {}){ Изображение("меню") .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) HStack{ VStack{ Image("logo-social") .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) Text("Your Name") .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Публикации") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ Text("100") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Текст("Последователи") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ Text("1000") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10) Кнопка(действие: {}){ Text("Редактировать профиль") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() Разделитель() TimelineView().padding(.leading, 10) Разделитель() BottomView() } } } } struct ProfileView_Previews: PreviewProvider { static var previews: some View { ProfileView() } }
И наконец…
У нас есть полноценный ProfileView:
Как же это было здорово!
Заключение
Сегодня вы узнали, как смоделировать представление профиля в вашем приложении. Пока это только имитация, но со временем мы добавим ей функциональности.
Вы узнали, как создавать и повторно использовать компоненты в SwiftUI и как красиво использовать их для создания сложного представления. Потрясающе!
В следующей статье мы создадим несколько других представлений!
Оставайтесь с нами!
Ссылка
- Первая часть этого цикла – статья Instagram Clone.
- Часть 2 – статья Swift Instagram Clone.
- Часть 3 – статья Профиль клона Instagram.
- Часть 4 – статья Insta Clone Homeview.
- Загрузите проект iOS Instagram Clone с исходным кодом и начните использовать Back4App.
Зарегистрируйтесь сейчас в Back4App и начните создавать свое приложение Instagram Clone.
В чем преимущество SwiftUI?
Если вы хотите создать клоны, такие как приложение Instagram, SwiftUI сотворит чудо.
Он помогает в создании сложных интерфейсов
Повторное использование компонентов
Процесс повторного использования интерфейсов прост и легок
Вам нужно только иметь знания, чтобы повторно использовать эти компоненты вместе.
Что такое Hstack, Vstack и Zstack?
Эти названия связаны из-за представительства в США. SwiftUI создает свои
пользовательские интерфейсы по следующим шаблонам.
-UI создается вертикально с помощью вертикального стека
-Он создается горизонтально с помощью горизонтального стека
-Перекрывающиеся представления создаются с помощью ZStack
Какая лучшая функция SwiftUI?
Лучшая функция SwiftUI — возможность повторного использования. Вы можете повторно использовать компоненты снова и снова для создания представлений. Это применимо ко всем приложениям. Вам нужно знать, как работать с этими компонентами.