Индикатор прогресса с ´ProgressView´ в SwiftUI

Индикатор прогресса с ´ProgressView´ в SwiftUI

Как устроен ProgressView. Как настроить внешний вид: спиннер и прогресс-бар.


Имя Автора
Автор Никита Россик Увлекаюсь разработкой под .

Содержание


Чтобы обозначить фоновую работу в приложении, используют ProgressView.


Неопределенный прогресс

Добавим ProgressView():

struct ContentView: View {
    
    var body: some View {
        VStack(spacing: 40) {
            ProgressView()
            Divider()
            ProgressView("Loading")
                .tint(.pink)
        }
    }
}


По умолчанию SwiftUI определяет вращающийся бар загрузки (спиннер), а модификатор .tint() меняет цвет бара.


Определенный прогресс

Используем явный индикатор — инициализируем вью:

struct ContentView: View {
    
    let totalProgress: Double = 100
    @State private var progress = 0.0
    
    var body: some View {
        VStack(spacing: 40) {
            currentTextProgress
            
            ProgressView(value: progress, total: totalProgress)
                .padding(.horizontal, 40)
            
            loadResetButtons
        }
    }
}

И добавим в экстеншен:

extension ContentView {

    private var currentTextProgress: Text {
        switch progress {
        case 5..


Если нажмём на Load more, то начнётся загрузка. Текст показывает прогресс, а кнопка Reset нужна для сброса. Когда загрузка закончится, текст на экране изменится, а кнопка Load more станет неактивной.

Сделаем симуляцию прогресса c таймером:

// filename: TimerProgressView.swift

struct TimerProgressView: View {
    
    let timer = Timer
        .publish(every: 0.05, on: .main, in: .common)
        .autoconnect()
    
    let downloadTotal: Double = 100
    @State private var progress: Double = 0
    
    var body: some View {
        VStack(spacing: 40) {
            Text("Downloading: (Int(progress))%")
            
            ProgressView(value: progress, total: downloadTotal)
                .tint(progress < 50 ? .pink : .green)
                .padding(.horizontal)
                .onReceive(timer) { _ in
                    if progress < downloadTotal { progress += 1 }
                }
        }
    }
}


Событие вызывается несколько раз при помощи таймера. Код:

let timer = Timer.publish(every: 0.05, on: .main, in: .common).autoconnect()

Таймер срабатывает каждые 0,05 секунд (50 миллисекунд), он должен работать в главном потоке и общем цикле common run loop. Run loop позволяет обрабатывать код, когда пользователь взаимодействует с интерфейсом. Таймер начнет отсчитывать время моментально.

Когда progress достигнет downloadTotal значения, таймер остановится.

При достижении 50% загрузки индикатор позеленеет.

ProgressView — полоса загрузки, которая заполняется слева направо.

В документации Apple описан метод publish. Больше инициализаторов — в документации Xcode или на сайте.

Скриншот с сайта Apple Developer.

Дизайн

Чтобы создать кастомный дизайн для ProgressView, нужно наследоваться от протокола ProgressViewStyle. Объявим структуру RoundedProgressViewStyle c методом makeBody() и принимающим параметр конфигурации для стиля:

struct RoundedProgressViewStyle: ProgressViewStyle {
    
    let color: Color
    
    func makeBody(configuration: Configuration) -> some View {
        let fractionCompleted = configuration.fractionCompleted ?? 0
        
        RoundedRectangle(cornerRadius: 18)
            .frame(width: CGFloat(fractionCompleted) * 200, height: 22)
            .foregroundColor(color)
            .padding(.horizontal)
    }
}

Передадим RoundedProgressViewStyle(color: .cyan) в модификатор .progressViewStyle():

struct TimerProgressView: View {
    
    let timer = Timer
        .publish(every: 0.05, on: .main, in: .common)
        .autoconnect()
    
    let downloadTotal: Double = 100
    @State private var progress: Double = 0
    
    var body: some View {
        VStack(spacing: 40) {
            Text("Downloading: (Int(progress))%")
            
            ProgressView(value: progress, total: downloadTotal)
                .onReceive(timer) { _ in
                    if progress < downloadTotal { progress += 1 }
                }
                .progressViewStyle(
                    RoundedProgressViewStyle(color: .cyan)
                )
        }
    }
}

Теперь прогресс продолжается с середины в противоположные стороны:




Другие туториалы

Поиск и модификатор ´Searchable´ в SwiftUI

Поиск в SwiftUI. Работаем с модификатором Searchable.

Действия на сочетания клавиш в SwiftUI

Знакомимся с модификатором keyboardShortcut. Добавим модификаторы для клавиш .command, .option, .shift

Альтернативные иконки для тестов Product Page Optimization

Как добавить альтернативные иконки для A/B тестов на странице приложения.

Отступы Edge Insets для ´UIButton´

Как добавить отступ между картинкой и заголовком в кнопке. Как поместить иконку справа от заголовка.

В telegram-канале приходят уведомления о новых туториалах. В чате для iOS разработчиков ответят на вопросы.

Открыть Telegram-канал