June 10, 2024

71. Хитрость с тенями в SwiftUI

В статье 47 показывал как гибко можно настраивать тени. В этой статье покажу как можно настроить тени в горизонтальной коллекции, чтобы они не обрезались фреймом.

Макет

Макет от дизайнера

Предположим, что дизайнер принес нам такой макет и наша задача сверстать такую коллекцию.

Для простоты: высота коллекции всегда равна 140, радиус тени - 8.

Верстаем

На первый взгляд ничего сложного, напишем код:

ScrollView(.horizontal) {
  LazyHStack(spacing: 16) {
    ForEach(1..<10, id: \.self) { item in
      RoundedRectangle(cornerRadius: 16)
        .fill(.white)
        .frame(width: 140, height: 140) // габариты ячейки по макету
        .overlay {
          Text("Text # \(item)")
        }
        .shadow(color: .blue, radius: 8)
    }
  }
  .padding(.horizontal) // отступы по макету
}
.frame(height: 140) // высота коллекции по макету
Промежуточный результат: тени обрезаны, и применяются в том числе к тексту

Проблемы

  1. Тень накладывается на все объекты в ячейке, т.е. и на текст, и на прямоугольник
  2. Тени обрезаются фреймом с настройкой высоты коллекции

Исправляем тень у текста

Воспользуемся знаниями из статьи 62 про drawingGroup и добавим 1 строку, чтобы убрать тени у текста:

ScrollView(.horizontal) {
  LazyHStack(spacing: 16) {
    ForEach(1..<10, id: \.self) { item in
      RoundedRectangle(cornerRadius: 16)
        .fill(.white)
        .frame(width: 140, height: 140) // габариты ячейки по макету
        .overlay {
          Text("Text # \(item)")
        }
        .drawingGroup() // <- убрали тени у текста
        .shadow(color: .blue, radius: 8)
    }
  }
  .padding(.horizontal) // отступы по макету
}
.frame(height: 140) // высота коллекции по макету
Промежуточный результат: убрали тень у текста, но тени у ячеек все еще обрезаны

Исправляем обрезку теней

Чтобы тени не обрезались, как вариант, можно воспользоваться паддингами (добавляем две строки):

ScrollView(.horizontal) {
  LazyHStack(spacing: 16) {
    ForEach(1..<10, id: \.self) { item in
      RoundedRectangle(cornerRadius: 16)
        .fill(.white)
        .frame(width: 140, height: 140) // габариты ячейки по макету
        .overlay {
          Text("Text # \(item)")
        }
        .drawingGroup() // <- убирает тень у текста
        .shadow(color: .blue, radius: 8)
    }
  }
  .padding(.horizontal) // отступы по макету
  .padding(.vertical, 20) // <- балансируем для теней
}
.padding(.vertical, -20) // <- балансируем для теней
.frame(height: 140) // высота коллекции по макету
Результат совпадает с макетом

Заключение

Если у вас появятся интересные варианты решения задачи - пишите в комментариях 👍

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