Skip to the content.

Зачем нужны нестандартные фигуры (Shapes) в самых обычных SwiftUI View?

Давайте сразу к делу. Есть такой макет.

Не буду подробно останавливаться на верстке, весьма обычный элемент.

Высота верхней половины динамическая, зависит от наполнения и, в частности, от рисунка внутри.

Верхнюю и нижнюю половины поместим в VStack, цвет у верхней выкрасим через ZStack, серую рамку вокруг сделаем через .background:

VStack {
    ZStack { 
        Rectangle()
            .fill(.indigo.opacity(0.4))
        ...
    }
    VStack {
        ...
    }
  ...
}.background(
RoundedRectangle(cornerRadius: 16)
.stroke(.gray, lineWidth: 1)
)

Выход цветной подложки сверху за пределы рамки классически исправляем через .cornerRadius(16):

VStack {

}.background(
    RoundedRectangle(cornerRadius: 16)
        .stroke(.gray, lineWidth: 1)
 )
.cornerRadius(16) // <= здесь

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

И, казалось бы, все хорошо, но позже дизайнер предложил смещать вертикально рисунок, чтобы картинка специально выходила за пределы рамки. Ну что ж, добавим .offset(y: …) к изображению рисунка:

И вот сталкиваемся с сайд-эффектом модификатора .cornerRadius(): он обрезает вью, к которому он применяется.

Ок, ситуацию исправило бы применение .cornerRadius не ко всему вью, а только к ZStack с цветом:

ZStack { 
    Rectangle()
        .fill(.indigo.opacity(0.4))
        .cornerRadius(16) // <= здесь
    ...
}

Но тогда закругляются и нижние углы у прямоугольника:

Значит нужен прямоугольник, у которого можно было бы настроить, какие углы закруглять. Из коробки такого нет. Отрисуем сами! Библиотеку с новой и другими фигурами можно найти здесь.

Применим расширение для View, построенного вокруг нового shape, для этого импортируем библиотеку:

import Shapes
    ...
ZStack { 
    Rectangle()
        .fill(.indigo.opacity(0.4))
        .cornerRadius(16, corners: .tops) // <= здесь
    ...
}

И вот теперь все хорошо:

А если нужен кроп, то применим модификатор .clipped ко всей вью:

VStack {
    ZStack { 
        Rectangle()
            .fill(.indigo.opacity(0.4))
            .cornerRadius(16, corners: .tops) // Здесь
        ...
    }
  VStack {
    ...
  }
    ...
}.background(
    RoundedRectangle(cornerRadius: 16)
        .stroke(.gray, lineWidth: 1)
 )
.clipped() // <= здесь

Результат будет тот же:

На этом все!✌🏻