Avatar ·

Как добавить индикатор загрузки (Activity Indicator) в SwiftUI

Привет! Я только начал изучать SwiftUI и столкнулся с проблемой. Мне нужно показать пользователю, что данные загружаются — например, когда приложение получает информацию из сети. В UIKit я использовал UIActivityIndicatorView, но в SwiftUI такого компонента не вижу. Я пробовал добавить обычный Text("Загрузка..."), но это выглядит непрофессионально. Хочу стандартный крутящийся индикатор, как в iOS. Вот пример моего кода, который загружает список пользователей: ```swift struct ContentView: View { @State private var users: [User] = [] @State private var isLoading = true var body: some View { NavigationView { List(users) { user in Text(user.name) } .navigationTitle("Пользователи") } .onAppear { loadUsers() } } func loadUsers() { // Здесь должна быть загрузка с сервера DispatchQueue.main.asyncAfter(deadline: .now() + 2) { users = [User(name: "Иван"), User(name: "Мария")] isLoading = false } } } ``` Но я не знаю, как правильно вставить сам индикатор. Пробовал использовать ProgressView() — он появляется, но не исчезает после загрузки. Подскажите, как сделать правильно? Желательно с примерами кода. Заранее спасибо! <<>>

Использование 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 только если вам действительно нужны его специфические возможности.