Продолжаю эпопею с модальными экранами на SwiftUI. В первой части в комментариях уже раскрыли главную интригу, но ничего, сегодня будет больше кода. Была задача, сделать ProgressView и SkeletonView. Вдруг кому-то пригодится, показываю.
ProgressView по дизайну должен был быть с градиентной полоской загрузки, по дефолту так нельзя сделать, поэтому я решила заменить полосочку — имитацией полоски загрузки. То есть у нас есть нормальный ProgressView, у него делаем невидимой полоску загрузки, а сверху имитация полоски загрузки — градиентная View.
Хотя, сказать по правде, я даже и нормальный ProgressView в итоге удалила, т к фейковый полностью дублирует его. В общем, меньше слов, больше кода!
struct GenerateReportView: View { @Environment(\.presentationMode) var presentationMode @State private var progress: Float = 0.0 @State private var progressIncrement: Float = 0.05 @State private var displayLink: Timer? = nil @State private var text = "Получаем данные с сервера..." var body: some View { VStack { Spacer() VStack { Image("reviewIcon") ZStack(alignment: .leading) { // Фейковый фон ProgressView RoundedRectangle(cornerRadius: 16) // Фейковая полоска загрузки для ProgressView с градиентом RoundedRectangle(cornerRadius: 16) .fill(LinearGradient(gradient: Gradient(colors: [Color(UIColor(hex: "#5C4EF2")), Color(UIColor(hex: "#1A96FF"))]), startPoint: .leading, endPoint: .trailing)) } Text(text) ... } } } }
Что здесь происходит: создаём два RoundedRectangle()
высотой 8 и накладываем их друг на друга в ZStack
. Далее прописываем второму LinearGradient
и в общем-то всё. Сделала для демонстрации фейковый таймер прогресса, по желанию можно заменить на данные прогресса из API. По мере загрузки данных меняется надпись под прогрессом загрузки.
// Function to start fake progress private func startFakeProgress() { displayLink = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in if self.progress < 0.99 { self.progress = min(self.progress + self.progressIncrement, 0.99) self.updateText() self.adjustProgressIncrement() } else { self.completeProgress() } } } // Function to update text based on progress private func updateText() { switch progress { case 0.0...0.19: text = "Получаем данные с сервера..." case 0.2...0.498: text = "Обновляем данные с сервера..." case 0.5...0.598: text = "Нужно ещё немного времени..." case 0.599...0.698: text = "Скоро загрузится..." case 0.699...0.89: text = "Ещё чуть-чуть..." case 0.899...0.999: text = "Уже почти..." default: text = "Получаем данные с сервера..." } } // Function to adjust progress increment as it gets closer to completion private func adjustProgressIncrement() { switch progress { case 0.0...0.19: progressIncrement /= 1.0 case 0.2...0.89: progressIncrement /= 1.1 case 0.9...0.99: progressIncrement /= 1.12 default: break } } // Function to complete progress quickly once the server responds private func completeProgress() { displayLink?.invalidate() displayLink = nil // Complete the progress in 1 second withAnimation(.linear(duration: 1.0)) { progress = 1.0 } // Call the delegate function if needed // delegate?.progressDone() } // Call this function when you receive the server response func serverResponseReceived() { completeProgress() }
Теперь перейдём к SkeletonView.
Его делать гораздо геморройнее. Для начала я создала общую структуру SkeletonLoadingView
, которая может на входе принимать любую форму, размер и цвет. После этого в любом месте кода можем просто добавить необходимое количество этих View.
struct SkeletonLoadingView<ShapeType: Shape>: View { @State private var animationPosition: CGFloat = -1 var width: CGFloat = 100 var height: CGFloat = 10 let shape: ShapeType let animation: Animation let gradient: Gradient var body: some View { shape .fill(self.gradientFill()) .frame(width: width, height: height) .onAppear { withAnimation(animation) { animationPosition = 2 } } } private func gradientFill() -> LinearGradient { return LinearGradient(gradient: gradient, startPoint: .init(x: animationPosition - 1, y: animationPosition - 1), endPoint: .init(x: animationPosition + 1, y: animationPosition + 1)) } }
Ну и чтобы добавить 4 полоски на мой экран, я сделала вот так:
VStack(alignment: .leading, spacing: 8) { SkeletonLoadingView(width: 350, shape: RoundedRectangle(cornerRadius: 8), animation: .easeIn(duration: 1).repeatForever(autoreverses: true), gradient: Gradient(colors: [Color.blue, Color.white])) SkeletonLoadingView(width: 380, shape: RoundedRectangle(cornerRadius: 8), animation: .easeIn(duration: 1).repeatForever(autoreverses: true), gradient: Gradient(colors: [Color.blue, Color.white])) SkeletonLoadingView(width: 350, shape: RoundedRectangle(cornerRadius: 8), animation: .easeIn(duration: 1).repeatForever(autoreverses: true), gradient: Gradient(colors: [Color.blue, Color.white])) SkeletonLoadingView(width: 180, shape: RoundedRectangle(cornerRadius: 8), animation: .easeIn(duration: 1).repeatForever(autoreverses: true), gradient: Gradient(colors: [Color.blue, Color.white])) }
Естественно, этот код тоже лучше вынести в отдельный модуль реализации. Но вот на этом этапе я уже начала соединять View и логику и тут-то у меня закрались некоторые подозрения… Вот мы и подошли к главной интриге — а, собственно, зачем нам SkeletonView
, если я уже сделала ProgressView
?
Получается, что на этапе показа экрана с описаниями — они уже все у нас подгружены и Skeleton точно не вызовется. На этапе генерации описания — показываем ProgressView
. То есть SkeletonView
оказался не нужен. Ну, бывает…
Полный код, как обычно, на моём GitHub.
В качестве бонуса добавила там ещё один вариант реализации Skeleton — BreathingSkeletonText
. Там используется уже по дефолту RoundedRectangle (или можно изменить на свой), добавлены цвета и остальные параметры. Подойдёт, если вы уже заранее точно знаете какие фигуры и цвета будете использовать для Skeleton.
Напишите пожалуйста в комментариях, какие вам ещё темы интересны? У меня есть всякие мини видео по 10 секунд где я делаю какие-нибудь забавные мелкие штуки на SwiftUI чисто для тренировки. Могу так же дублировать сюда описание и код.
Например вот пост в ТГ где я делаю снежинки или вот создаю простую игру «кошки-мышки».
ссылка на оригинал статьи https://habr.com/ru/articles/853600/
Добавить комментарий