Вот посмотрите на конечный продукт. Многие из вас, кто использовал Instagram, будут знакомы. И что самое приятное, это не обязательно строго относиться к публикации фотографий в социальных сетях. Вы можете использовать этот тип представления, чтобы продемонстрировать что угодно. Это могут быть товары, новостные статьи или другие фотографии. Давайте начнем!
Перед тем, как начать, рассмотрите возможность подписки по этой ссылке, и если вы не читаете ее на TrailingClosure.com, приходите к нам как-нибудь!
Начиная
Если вы работаете в Xcode, вам понадобится несколько фотографий для этого проекта. Я собрал несколько из раздела Nature Unsplash для использования в этом уроке. Вы можете скачать их здесь.
Когда они у вас есть, запустите Xcode, создайте новый проект и добавьте фотографии в папку Assets.xcassets
.
Создание LoadingRectangle
представления
LoadingRectangle
будет использоваться для отображения времени для каждой фотографии. Он состоит из двух Rectangle
просмотров, расположенных одно над другим. Верх Rectangle
со временем увеличивает свою ширину, чтобы закрыть другую.
- Начните с создания нового представления SwiftUI под названием
LoadingRectangle
. - Удалите созданный для вас
Text
и замените его наGeometryReader
. Это даст вам ссылку на кадр, который мы будем использовать через секунду. - Затем, чтобы сложить оба прямоугольника, добавьте
ZStack
вGeometryReader
и поместите внутрь двухRectangle
Views. Убедитесь, что вы выровнялиZStack
.leading
, чтобы наша верхняяRectangle
росла с левой стороны.
Вот что у вас должно получиться на данный момент:
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
Rectangle()
}
}
}
Затем нам нужно изменить верх Rectangle
, чтобы он со временем менял размер. Для этого нужно объявить переменную progress
.
var progress: CGFloat
Затем измените второй Rectangle
так, чтобы его ширина изменялась в соответствии с переменной progress
.
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
Rectangle()
.frame(width: geometry.size.width * self.progress, height: nil, alignment: .leading)
}
}
}
Наконец, вы можете стилизовать прямоугольники по своему вкусу. Я пошел дальше и изменил радиус их углов, а также цвет. Окончательный код body
ниже.
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(Color.white.opacity(0.3))
.cornerRadius(5)
Rectangle()
.frame(width: geometry.size.width * self.progress, height: nil, alignment: .leading)
.foregroundColor(Color.white.opacity(0.9))
.cornerRadius(5)
}
}
}
Объединение изображений и LoadingRectangle
Перейдем к ContentView.swift
. Xcode сгенерировал этот файл при создании проекта.
Начните с объявления массива имен изображений вверху. Это должны быть названия фотографий, которые мы добавили ранее, и изображения, которые мы отображаем в «Истории».
var imageNames:[String] = ["image01","image02","image03","image04","image05","image06","image07"]
Подобно тому, что мы сделали в LoadingRectangle
, замените Text
на ZStack
, завернутый в GeometryReader
, и поместите внутрь Image
и горизонтальный стек LoadingRectangle
.
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .top) {
Image(self.imageNames[0])
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
.frame(width: geometry.size.width, height: nil, alignment: .center)
.animation(.none)
HStack(alignment: .center, spacing: 4) {
ForEach(self.imageNames.indices) { x in
LoadingRectangle(progress: 1.0)
.frame(width: nil, height: 2, alignment: .leading)
.animation(.linear)
}
}.padding()
}
}
}
Я пошел вперед и добавил несколько вещей ради экономии времени, но не стесняйтесь вернуться и поэкспериментировать с некоторыми настройками, чтобы почувствовать то, что я сделал (в частности, с модификаторами для изображения и рамки для LoadingRectangle
)
На данный момент мы добавили в приведенный выше код два заполнителя.
- Для текущего отображаемого изображения.
Image(self.imageNames[0])
- За прогресс на каждом
LoadingRectangle
.LoadingRectangle(progress: 1.0)
Чтобы двигаться вперед, нам нужно создать StoryTimer
, который управляет перемещением от фото к фото, а также передает его прогресс в LoadingRectangle
.
Создание StoryTimer
Последний кусок, который нам нужно создать, - это таймер. Это будет ObservableObject, который публикует свою progress
переменную для остальных наших частей.
class StoryTimer: ObservableObject {
@Published var progress: Double
private var interval: TimeInterval
private var max: Int
private let publisher: Timer.TimerPublisher
private var cancellable: Cancellable?
init(items: Int, interval: TimeInterval) {
self.max = items
self.progress = 0
self.interval = interval
self.publisher = Timer.publish(every: 0.1, on: .main, in: .default)
}
func start() {
self.cancellable = self.publisher.autoconnect().sink(receiveValue: { _ in
var newProgress = self.progress + (0.1 / self.interval)
if Int(newProgress) >= self.max { newProgress = 0 }
self.progress = newProgress
})
}
}
Взгляните на инициализатор для StoryTimer
. Требуется количество элементов в нашей «Истории» и TimeInterval
, сколько времени мы должны отображать каждый элемент.
Когда появится наш ContentView
, мы вызовем функцию start()
, чтобы начать получать значения из TimerPublisher
. Каждый раз при получении нового значения мы обновляем переменную progress
, которая публикуется нашим классом StoryTimer
. Это, в свою очередь, заставляет наш ContentView
обновлять наш LoadingRectangle
с правильным прогрессом и отображает правильное изображение на экране.
Удаление заполнителей в ContentView
Сначала создайте экземпляр StoryTimer
в верхней части ContentView
.
@ObservedObject var storyTimer: StoryTimer = StoryTimer(items: 7, interval: 3.0)
Затем замените строку, в которой вы объявили Image
, на следующую:
Image(self.imageNames[Int(self.storyTimer.progress)])
Что это значит, так это захватить прогресс из StoryTimer
и выбрать соответствующее изображение через его индекс.
Второй заполнитель, который нам нужно обновить, - это индикатор прогресса нашего LoadingRectangle
. Замените его экземпляр следующим:
LoadingRectangle(progress: min( max( (CGFloat(self.storyTimer.progress) - CGFloat(x)), 0.0) , 1.0) )
Может показаться, что здесь много чего происходит, но на самом деле это всего лишь простая математика. Диапазон значений self.storyTimer.progress
от 0
до N
(количество отображаемых фотографий). В то время как Loadingrectangle
требуется значение прогресса от 0.0
до 1.0
. Мы выполняем преобразование на основе индекса каждого LoadingRectangle
(в данном случае x
)
Запуск таймера
Наконец, внизу ZStack
в ContentView.swift
вам нужно запустить StoryTimer
, когда появится представление.
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .top) {
/* Image and LoadingRectangles Here */
}
.onAppear { self.storyTimer.start() }
.onDisappear {self.storyTimer.cancel() }
}
}
Если все прошло хорошо, вы сможете нажать кнопку запуска и получить то, что вы видите ниже.
Дополнительный кредит
Если вы хотите пройти лишнюю милю, вы можете реализовать TapGesture
s для каждой половины экрана, чтобы циклически просматривать фотографии, как в реальном приложении Instagram.
Добавьте TapGesture
в ContentView.swift
Добавьте следующий HStack
внизу ZStack
внутри тела вашего основного ContentView
.
HStack(alignment: .center, spacing: 0) {
Rectangle()
.foregroundColor(.clear)
.contentShape(Rectangle())
.onTapGesture {
self.storyTimer.advance(by: -1)
}
Rectangle()
.foregroundColor(.clear)
.contentShape(Rectangle())
.onTapGesture {
self.storyTimer.advance(by: 1)
}
}
Изменить StoryTimer
Затем нам нужно добавить функцию к классу StoryTimer
, которая позволяет нам увеличивать или уменьшать его прогресс. Мы хотим перейти к следующей фотографии, но не забегать слишком далеко вперед и сократить время ее показа. Функция ниже вычисляет текущий индекс элемента, а затем увеличивает прогресс (или вычитает, если вы укажете отрицательное число).
func advance(by number: Int) {
let newProgress = max((Int(self.progress) + number) % self.max , 0)
self.progress = Double(newProgress)
}
Все сделано
Попробуй еще раз! Попробуйте нажимать слева и справа на экране, чтобы увидеть, как фотографии перемещаются вперед и назад. Самое приятное то, что LoadingRectangle
по-прежнему безупречно анимируется.
Поддержка будущих сообщений
Если вам понравился этот пост, рассмотрите возможность подписки на мой веб-сайт, используя эту ссылку, и если вы не читаете это на TrailingClosure.com, приходите к нам как-нибудь!