Skip to content

Commit 74a4aa5

Browse files
committed
progress_view_swiftui
1 parent 8899109 commit 74a4aa5

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
В процессе написания приложения, у нас появляются методы, которые выполняют фоновую работу, будь то работа с сетью или обработка данных. Для того, чтобы обозначить такую работу, нам поможет `ProgressView` — вью, которое показывает ход выполнения задачи/работы.
2+
3+
4+
## Неопределенный прогресс
5+
6+
Для того, чтобы создать индикатор загрузки, поместим `ProgressView()` в нашу вью:
7+
8+
```swift
9+
struct ContentView: View {
10+
var body: some View {
11+
VStack(spacing: 40) {
12+
ProgressView()
13+
Divider()
14+
ProgressView("Loading")
15+
.tint(.pink)
16+
}
17+
}
18+
```
19+
20+
21+
[Indeterminate activity indicator](https://cdn.ivanvorobei.by/websites/sparrowcode.io/mastering-progressview-swiftui/indeterminate_activity_indicator.mov)
22+
23+
По умолчанию SwiftUI определяет вращающийся бар загрузки (спиннер), которым обозначают некоторую работу в фоне.
24+
Обратите внимание, что с помощью модификатора `.tint()` можно изменить цвет бара.
25+
Теперь посмотрим на другой способ инициализирования.
26+
27+
28+
## Определенный прогресс
29+
30+
В отличии от неопределенного, мы можем показать прогресс, который имеет явный индикатор.
31+
Для этого инициализируем вью способом ниже:
32+
33+
```swift
34+
struct ContentView: View {
35+
let totalProgress: Double = 100
36+
@State private var progress = 0.0
37+
38+
var body: some View {
39+
VStack(spacing: 40) {
40+
currentTextProgress
41+
42+
ProgressView(value: progress, total: totalProgress)
43+
.padding(.horizontal, 40)
44+
45+
loadResetButtons
46+
}
47+
}
48+
}
49+
50+
extension ContentView {
51+
private var currentTextProgress: Text {
52+
switch progress {
53+
case 5..<totalProgress: return Text("Current progress: \(Int(progress))%")
54+
case totalProgress...: return Text("Loading complete")
55+
default: return Text("Start loading")
56+
}
57+
}
58+
59+
private var loadResetButtons: some View {
60+
HStack(spacing: 20) {
61+
Button("Load more") {
62+
withAnimation { progress += Double.random(in: 5...20) }
63+
}
64+
.disabled(!progress.isLessThanOrEqualTo(totalProgress))
65+
66+
Button(role: .destructive) {
67+
progress = 0
68+
} label: {
69+
Text("Reset")
70+
}
71+
.tint(.red)
72+
.disabled(progress.isZero)
73+
}
74+
.buttonStyle(.bordered)
75+
}
76+
}
77+
```
78+
79+
[Determinate activity indicator](https://cdn.ivanvorobei.by/websites/sparrowcode.io/mastering-progressview-swiftui/determinate_activity_indicator.mov)
80+
81+
82+
По нажатию кнопки `Load more` начинается загрузка, текст показывает текущий прогресс, а кнопка `Reset` становится доступной для нажатия и сброса прогресса. По достижению загрузки, текст на экране сообщает о завершении загрузки и кнопка `Load more` становится неактивной.
83+
84+
Для симуляции прогресса, я покажу еще один пример с таймером.
85+
86+
```swift
87+
// filename: TimerProgressView.swift
88+
89+
struct TimerProgressView: View {
90+
let timer = Timer
91+
.publish(every: 0.05, on: .main, in: .common)
92+
.autoconnect()
93+
94+
let downloadTotal: Double = 100
95+
@State private var progress: Double = 0
96+
97+
var body: some View {
98+
VStack(spacing: 40) {
99+
Text("Downloading: \(Int(progress))%")
100+
101+
ProgressView(value: progress, total: downloadTotal)
102+
.tint(progress < 50 ? .pink : .green)
103+
.padding(.horizontal)
104+
.onReceive(timer) { _ in
105+
if progress < downloadTotal { progress += 1 }
106+
}
107+
}
108+
}
109+
}
110+
```
111+
112+
[Timer progress](https://cdn.ivanvorobei.by/websites/sparrowcode.io/mastering-progressview-swiftui/timer_progress.mov)
113+
114+
115+
В данном примере, мы вызываем событие многократно, используя таймер.
116+
Код для создания таймера выглядит так:
117+
118+
```swift
119+
let timer = Timer.publish(every: 0.05, on: .main, in: .common).autoconnect()
120+
```
121+
122+
Поясню некоторые моменты:
123+
124+
1. Таймер срабатывает каждые 0.05 секунд, что равняется 50 миллисекунд.
125+
2. Таймер должен работать в главном потоке.
126+
3. Таймер должен работать в общем цикле(common run loop). Run loop позволяет обрабатывать работающий
127+
код, когда пользователь делает что-либо, например нажимает на кнопку.
128+
4. Таймер подключается немедленно и начинается сразу отчитывать время.
129+
130+
Когда `progress` достигнет `downloadTotal` значения, таймер остановится.
131+
При достижении 50% загрузки, индикатор меняет цвет на зеленый.
132+
133+
При таком объявлении, `ProgressView` выглядит как полоса загрузки, которая заполняется слева направо.
134+
Так можно показать пользователю, что загрузка данных выполняется в зависимости от размера файла.
135+
136+
137+
Описание метода `publish` доступно в [документации Apple](https://developer.apple.com/documentation/foundation/timer/3329589-publish).
138+
139+
Больше инициализатор можно найти в документации Xcode или [на сайте](https://developer.apple.com/documentation/swiftui/progressview).
140+
141+
![Documentation SwiftUI ProgressView](https://cdn.ivanvorobei.by/websites/sparrowcode.io/mastering-progressview-swiftui/progressview_init.png)
142+
143+
144+
## Дизайн
145+
146+
147+
SwiftUI предоставляет протокол `ProgressViewStyle`, который позволяет создавать собственный дизайн для `ProgressView`.
148+
Например, вы можете настроить вид и взаимодействие прогресса, создав стиль, который наследуется от протокола `ProgressViewStyle`.
149+
150+
151+
Для этого объявим структуру `RoundedProgressViewStyle`, которая наследуется от протокола `ProgressViewStyle` и содержит метод `makeBody()`, принимающий параметр конфигурации для нашего стиля.
152+
153+
```swift
154+
struct RoundedProgressViewStyle: ProgressViewStyle {
155+
let color: Color
156+
157+
func makeBody(configuration: Configuration) -> some View {
158+
let fractionCompleted = configuration.fractionCompleted ?? 0
159+
160+
RoundedRectangle(cornerRadius: 18)
161+
.frame(width: CGFloat(fractionCompleted) * 200, height: 22)
162+
.foregroundColor(color)
163+
.padding(.horizontal)
164+
}
165+
}
166+
```
167+
168+
Далее вернемся к `TimerProgressView.swift` и передадим `RoundedProgressViewStyle(color: .cyan)` в модификатор `.progressViewStyle()`.
169+
170+
Теперь код выглядит так:
171+
172+
```swift
173+
struct TimerProgressView: View {
174+
let timer = Timer
175+
.publish(every: 0.05, on: .main, in: .common)
176+
.autoconnect()
177+
178+
let downloadTotal: Double = 100
179+
@State private var progress: Double = 0
180+
181+
var body: some View {
182+
VStack(spacing: 40) {
183+
Text("Downloading: \(Int(progress))%")
184+
185+
ProgressView(value: progress, total: downloadTotal)
186+
.onReceive(timer) { _ in
187+
if progress < downloadTotal { progress += 1 }
188+
}
189+
.progressViewStyle(
190+
RoundedProgressViewStyle(color: .cyan)
191+
)
192+
}
193+
}
194+
}
195+
```
196+
197+
Обратите внимание, что прогресс начинается не слева направо, а с середины в противоположные стороны.
198+
199+
[RoundedProgressViewStyle](mov)
200+
201+
202+
## Доступность
203+
204+
205+
OS | Version
206+
--- | ---
207+
iOS | 14.0+
208+
iPadOS | 14.0+
209+
macOS | 11.0+
210+
tvOS | 14.0+
211+
watchOS | 7.0+
212+
213+
---
214+
215+
На этом всё. Подписывайтесь на [канал в телеграм](https://t.me/sparrowcode), чтобы не пропускать выходы туториалов и новостей, а вопросы задавайте в [чате](https://t.me/+GpBH8oKHAjcyZmVi).

0 commit comments

Comments
 (0)