Как добавить индикатор загрузки (Activity Indicator) в SwiftUI
Использование ProgressView в SwiftUI
В SwiftUI для отображения индикатора загрузки используется компонент ProgressView. Он заменяет UIActivityIndicatorView из UIKit и отлично интегрируется с декларативным подходом. Главное — правильно управлять его видимостью с помощью состояния (@State).
Базовое решение
Вот как исправить ваш код. Просто оберните список в условное отображение, используя флаг isLoading:
struct ContentView: View {
@State private var users: [User] = []
@State private var isLoading = true
var body: some View {
NavigationView {
Group {
if isLoading {
// Показываем индикатор, пока загружаются данные
ProgressView("Загрузка пользователей...")
.scaleEffect(1.5) // Увеличиваем размер
.padding()
} else {
// После загрузки показываем список
List(users) { user in
Text(user.name)
}
}
}
.navigationTitle("Пользователи")
}
.onAppear {
loadUsers()
}
}
func loadUsers() {
// Имитация загрузки
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
users = [
User(name: "Иван"),
User(name: "Мария"),
User(name: "Алексей")
]
isLoading = false // Выключаем индикатор
}
}
}
struct User: Identifiable {
let id = UUID()
let name: String
}
Продвинутые настройки
- Кастомизация текста: Вы можете передать любое описание в
ProgressView("Ваш текст")или убрать его совсем:ProgressView(). - Изменение размера: Используйте модификатор
.scaleEffect()для увеличения или уменьшения индикатора. - Стилизация: Примените
.tint(.blue)для изменения цвета спиннера. - Позиционирование: Оберните в
VStackилиHStackдля точного размещения.
Альтернативный подход: overlay
Если вы хотите, чтобы индикатор появлялся поверх существующего контента (например, поверх уже отрисованного списка), используйте модификатор .overlay:
List(users) { user in
Text(user.name)
}
.overlay {
if isLoading {
ProgressView()
.scaleEffect(2)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black.opacity(0.1)) // Полупрозрачный фон
}
}
Для загрузки из сети (асинхронно)
Если вы используете async/await, индикатор можно комбинировать с задачей:
struct AsyncContentView: View {
@State private var data: [String] = []
@State private var isLoading = false
var body: some View {
List(data, id: \.self) { item in
Text(item)
}
.overlay {
if isLoading {
ProgressView()
.scaleEffect(2)
}
}
.task {
isLoading = true
defer { isLoading = false }
// Ваш асинхронный запрос
data = await fetchData()
}
}
func fetchData() async -> [String] {
// Имитация сетевого запроса
try? await Task.sleep(nanoseconds: 2_000_000_000)
return ["Результат 1", "Результат 2"]
}
}
Важные моменты
- Всегда обновляйте
isLoadingна главном потоке (SwiftUI делает это автоматически при использовании@State). - Не забывайте сбрасывать индикатор в
falseв случае ошибки, иначе он будет крутиться вечно. - Для более сложных сценариев (например, загрузка с прогрессом) используйте
ProgressView(value: total:)с привязкой к проценту выполнения.
Теперь ваш интерфейс будет выглядеть профессионально, а пользователь всегда будет знать, что приложение работает!
<<Использование UIKit-компонента через UIViewRepresentable
Если вам нужен именно классический UIActivityIndicatorView из UIKit (например, для сохранения старого дизайна), вы можете обернуть его в SwiftUI с помощью протокола UIViewRepresentable:
struct ActivityIndicator: UIViewRepresentable {
@Binding var isAnimating: Bool
var style: UIActivityIndicatorView.Style = .medium
func makeUIView(context: Context) -> UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(style: style)
indicator.hidesWhenStopped = true
return indicator
}
func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
if isAnimating {
uiView.startAnimating()
} else {
uiView.stopAnimating()
}
}
}
// Использование в SwiftUI:
struct ContentView: View {
@State private var isLoading = true
var body: some View {
ActivityIndicator(isAnimating: $isLoading, style: .large)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isLoading = false
}
}
}
}
Когда это может пригодиться
- Тонкая настройка: UIKit-индикатор поддерживает больше кастомных параметров (цвет, стиль, анимация).
- Совместимость: Если вы переносите старый код и не хотите менять логику.
- Особые эффекты: Например, можно добавить свою анимацию остановки.
Однако для большинства случаев ProgressView из первого ответа — это более современный и простой вариант. Используйте UIKit только если вам действительно нужны его специфические возможности.