December 30, 2023

47. Изучаем тени в SwiftUI

Посмотрим как настраиваются тени в SwiftUI, заодно поработаем с выбором цвета и слайдерами.

Сделаем экран, на котором можно настроить все параметры тени и посмотреть на результат:

Демо настройки теней

Сделаем свойство для получения случайного цвета:

extension Color {
  static var random: Self {
    Color(
      red: .random(in: 0...1),
      green: .random(in: 0...1),
      blue: .random(in: 0...1)
    )
  }
}

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

struct ShadowModel {
  var color: Color
  var radius: CGFloat
  var x: CGFloat
  var y: CGFloat
   
  static var randomized: Self {
    .init(
      color: .random,
      radius: CGFloat.random(in: 0...16),
      x: CGFloat.random(in: -16...16),
      y: CGFloat.random(in: -16...16)
    )
  }
}

Диапазон значений поставлен для примера, можете экспериментировать с любыми значениями.

Создадим цвет для модификатора с "карточкой", внутри которого будет применяться тень:

Цвет для "карточки"

Сделаем сам модификатор для "карточки", который принимает на вход отступы для контента и параметры тени:

struct CardModifier: ViewModifier {
  let padding: CGFloat
  let shadowModel: ShadowModel

  func body(content: Content) -> some View {
    content
      .padding(padding)
      .background {
        RoundedRectangle(cornerRadius: 12, style: .continuous)
          .foregroundStyle(.cardBackground) // цвет из ассетов
          .shadow(
            color: shadowModel.color,
            radius: shadowModel.radius,
            x: shadowModel.x,
            y: shadowModel.y
          )
      }
  }
}

extension View {
  func insideCard(
    padding: CGFloat = 12,
    shadowModel: ShadowModel
  ) -> some View {
    modifier(
      CardModifier(
        padding: padding,
        shadowModel: shadowModel
      )
    )
  }
}

Сверстаем вьюшку с настройками:

    Group {
      ColorPicker( // <- а вот и выбор цвета
        "Цвет тени",
        selection: $shadowModel.color,
        supportsOpacity: false
      )
      Divider()
      VStack {
        Slider(value: $shadowModel.radius, in: 0...16, step: 1)
        Text("Радиус: \(makeRoundedText(for: shadowModel.radius))")
      }
      VStack {
        Slider(value: $shadowModel.x, in: -16...16)
        Text("Отступ по оси X: \(makeRoundedText(for: shadowModel.x))")
      }
      VStack {
        Slider(value: $shadowModel.y, in: -16...16)
        Text("Отступ по оси Y: \(makeRoundedText(for: shadowModel.y))")
      }
      Button("Randomize") {
        withAnimation { shadowModel = .randomized }
      }
      .buttonStyle(.borderedProminent)
      .padding(.top, 16)
    }
    .padding(.vertical, 4)
    .padding(.horizontal)
  }

И теперь экран:

struct ShadowExample: View {
  @State private var shadowModel = ShadowModel.randomized

  var body: some View {
    VStack(spacing: 0) {
      settingsView
      Rectangle()
        .frame(height: 125)
        .foregroundStyle(.white)
        .overlay(alignment: .bottom) {
          Text("Light mode preview")
            .insideCard(shadowModel: shadowModel)
            .padding(.bottom)
        }
      Rectangle()
        .ignoresSafeArea()
        .overlay(alignment: .top) {
          Text("Dark mode preview")
            .insideCard(shadowModel: shadowModel)
            .padding(.top)
            .environment(\.colorScheme, .dark)
        }
    }
  }
  // остальной код
}

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