August 16, 2024

80. Анимируем spacing в стеках

В горизонтальных и вертикальных стеках в SwiftUI есть спейсинг - это расстояние между элементами. В этой статье покажу как можно анимировать этот спейсинг, и до кучи используем отрицательное значение.

Готовая вьюха

Демо готовой анимации. 4 круга сходятся в один и расходятся при изменении спейсинга.

По кнопке "Пуск" значение спейсинга меняется с минимального на максимальное (и обратно), где минимальное равно -20, а максимальное 30 (значения взяты для примера).

Реализация

Как обычно анимируем стейт-свойство:

struct AnimatedSpacingExample: View {
  /// Тут храним спейсинг для анимации
  @State private var hSpacing: CGFloat = 30
  private let numbers = [1, 2, 3, 4]
   
  var body: some View {
    VStack(spacing: 20) {
      Text("Спейсинг: \(String(format: "%.1f", hSpacing))")
      HStack(spacing: hSpacing) { // <- вот наш анимируемый спейсинг
        ForEach(numbers, id: \.self) { _ in
          Circle()
            .opacity(max(currentProgress, 0.2))
            .animation(.spring, value: hSpacing)
            .frame(width: circleSize, height: circleSize)
        }
      }
      .frame(height: 30)
      Slider(value: $hSpacing, in: -20...30)
      Button("Пуск", action: buttonAction)
    }
  }
   
  /// Размер фрейма для каждого круга
  private var circleSize: CGFloat {
    let maxSize: CGFloat = 20
    let minSize: CGFloat = 10
     
    // Нормализуем значение hSpacing в диапазоне от -20 до 30
    let normalizedSpacing = (hSpacing + 20) / (30 + 20)
     
    // Вычисляем размер круга
    return maxSize * (1 - normalizedSpacing) + minSize * normalizedSpacing
  }
   
  /// Текущий прогресс, где 1.0 соответствует спейсингу -20
  private var currentProgress: CGFloat {
    let min: CGFloat = -20.0
    let max: CGFloat = 30.0
     
    // Проверяем, находится ли значение вне диапазона
    if hSpacing <= min {
      return 1.0
    } else if hSpacing >= max {
      return 0.0
    } else {
      // Линейная интерполяция
      return (max - hSpacing) / (max - min)
    }
  }
  
  private func buttonAction() {
    if hSpacing > -20 {
      hSpacing = -20
    } else {
      hSpacing = 30
    }
  }
}

Заключение

Анимации в SwiftUI классные, и анимировать можно почти все, даже спейсинги и паддинги. Главное не забывать указывать спейсинг в рабочих проектах, потому что по дефолту он не равен нулю - в этом мы убедились тут.

Про линейную интерполяцию узнал случайно при подготовке статьи 🙂

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