October 21, 2023

37. Делаем модификатор с кастомным индикатором загрузки

В этой статье покажу простой способ сделать модификатор с анимированной сменой состояния загрузки, блокирующий контент от нажатий на время загрузки (пока ждем ответ сервера, например).

Сделаем такой экран:

Демо модификатора с кастомным индикатором загрузки

Для модификатора в первую очередь нам понадобится вьюшка с индикатором загрузки:

/// Вьюшка с анимированным индикатором загрузки
private struct LoadingIndicatorView: View {
  @State private var isAnimating = false

  var body: some View {
    Image(.loadingIndicator) // Картинка из ассетов проекта
      .resizable()
      .frame(width: 50, height: 50)
      .foregroundStyle(.white)
      .rotationEffect(Angle(degrees: isAnimating ? 360 : 0))
      .animation(
        .linear(duration: 2.0).repeatForever(autoreverses: false),
        value: isAnimating
      )
      .onAppear { isAnimating = true }
  }
}

Кстати, в Xcode 15 картинки из ассетов генерируются автоматически:

Теперь можно сделать сам модификатор:

/// Модификатор для отображения индикатора загрузки
///
/// Делает контент недоступным для нажатия в состоянии загрузки
struct LoadingIndicatorModifier: ViewModifier {
  let isLoading: Bool

  func body(content: Content) -> some View {
    ZStack {
      content
        .opacity(isLoading ? 0.5 : 1)
        .disabled(isLoading)
      if isLoading {
        LoadingIndicatorView()
      }
    }
    .animation(.default, value: isLoading)
  }
}

Все просто: если isLoading = true, то показываем прозрачный контент, иначе - обычный. Также делаем контент недоступным и анимируем изменения при смене isLoading.

Для удобства применения модификатора можно сделать доп. метод:

extension View {
  func loadingOverlay(if isLoading: Bool) -> some View {
    modifier(LoadingIndicatorModifier(isLoading: isLoading))
  }
}

Сам экран делается так:

struct LoadingIndicatorModifierExample: View {
  @State private var isLoading = false

  var body: some View {
    Color.black.ignoresSafeArea()
      .overlay {
        Button("Начать загрузку") {
          isLoading = true
        }
        .font(.title.bold())
        .tint(.yellow)
      }
      .loadingOverlay(if: isLoading)
  }
}

Если хочется, вместо LoadingIndicatorView можно использовать стандартный ProgressView.

Код для этой статьи можно посмотреть тут, а другие статьи - тут.