Как скруглить только определенные углы View в SwiftUI?
Всем привет! Я новичок в SwiftUI и столкнулся с проблемой: мне нужно скруглить только два верхних угла у прямоугольного View, а нижние оставить прямыми. В обычном UIKit я использовал UIBezierPath и CAShapeLayer, но в SwiftUI, похоже, нет прямого модификатора для выборочного скругления.
Я пробовал использовать .cornerRadius(20), но он скругляет все четыре угла. Также я читал про .clipShape(RoundedRectangle(cornerRadius: 20)), но это тоже не решает задачу — скругляются все углы.
Вот пример моего кода:
struct ContentView: View {
var body: some View {
VStack {
Text("Привет, мир!")
.padding()
.background(Color.blue)
.cornerRadius(20) // скругляет все углы
}
.padding()
}
}
Мне нужно, чтобы верхние углы были закруглены, а нижние оставались прямыми. Как это сделать в SwiftUI? Есть ли встроенный модификатор, или придётся использовать кастомные решения? Буду благодарен за примеры кода!
<<Использование кастомной формы с помощью модификатора .clipShape
В SwiftUI нет встроенного модификатора для выборочного скругления углов, но это легко решается созданием кастомной формы, которая наследует протокол Shape. Мы можем нарисовать путь с помощью UIBezierPath или встроенного Path из SwiftUI и указать, какие углы скруглять, а какие — нет.
Вот готовое решение:
struct RoundedCorners: Shape {
var topLeft: CGFloat = 0
var topRight: CGFloat = 0
var bottomLeft: CGFloat = 0
var bottomRight: CGFloat = 0
func path(in rect: CGRect) -> Path {
let minX = rect.minX
let minY = rect.minY
let maxX = rect.maxX
let maxY = rect.maxY
var path = Path()
// Начинаем с левого верхнего угла
path.move(to: CGPoint(x: minX + topLeft, y: minY))
// Верхняя сторона
path.addLine(to: CGPoint(x: maxX - topRight, y: minY))
// Правый верхний угол
path.addArc(center: CGPoint(x: maxX - topRight, y: minY + topRight),
radius: topRight,
startAngle: Angle(degrees: -90),
endAngle: Angle(degrees: 0),
clockwise: false)
// Правая сторона
path.addLine(to: CGPoint(x: maxX, y: maxY - bottomRight))
// Правый нижний угол
path.addArc(center: CGPoint(x: maxX - bottomRight, y: maxY - bottomRight),
radius: bottomRight,
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: 90),
clockwise: false)
// Нижняя сторона
path.addLine(to: CGPoint(x: minX + bottomLeft, y: maxY))
// Левый нижний угол
path.addArc(center: CGPoint(x: minX + bottomLeft, y: maxY - bottomLeft),
radius: bottomLeft,
startAngle: Angle(degrees: 90),
endAngle: Angle(degrees: 180),
clockwise: false)
// Левая сторона
path.addLine(to: CGPoint(x: minX, y: minY + topLeft))
// Левый верхний угол
path.addArc(center: CGPoint(x: minX + topLeft, y: minY + topLeft),
radius: topLeft,
startAngle: Angle(degrees: 180),
endAngle: Angle(degrees: 270),
clockwise: false)
return path
}
}
Теперь вы можете использовать эту форму с модификатором .clipShape:
struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.padding()
.background(Color.blue)
.clipShape(RoundedCorners(topLeft: 20, topRight: 20, bottomLeft: 0, bottomRight: 0))
}
}
Как это работает:
- Мы создаём структуру
RoundedCorners, которая принимает радиусы для каждого угла (по умолчанию 0). - В методе
path(in:)мы рисуем контур прямоугольника, используяaddArcдля скругления углов, где радиус больше 0. - Для углов с радиусом 0 мы просто рисуем прямые линии.
- Модификатор
.clipShapeобрезает view по заданной форме.
Дополнительные советы:
- Если вам нужно скруглить только два верхних угла, как в вашем примере, передайте радиусы только для них:
topLeft: 20, topRight: 20. - Вы можете комбинировать это с другими модификаторами, например, с
.overlayдля добавления границы с такими же скруглениями. - Для более сложных случаев (например, разные радиусы для каждого угла) эта форма легко расширяется.
Это решение полностью кастомизируемо и работает во всех версиях iOS, начиная с iOS 13. Оно также совместимо с macOS, watchOS и tvOS.
<<Альтернативное решение: использование модификатора .cornerRadius с наложением
Если вам не хочется создавать кастомную форму, можно использовать простой трюк: наложить один view на другой, чтобы скрыть скруглённые углы. Например, чтобы оставить скруглёнными только верхние углы, можно сделать так:
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
// Верхняя часть со скруглёнными углами
Text("Привет, мир!")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(20)
// Нижняя часть без скругления
Rectangle()
.fill(Color.blue)
.frame(height: 10) // маленькая полоска, чтобы перекрыть нижние углы
}
.frame(width: 200) // задайте нужную ширину
}
}
Как это работает:
- Мы используем
VStackс нулевым отступом между элементами (spacing: 0). - Верхний view скругляет все углы, но нижний view (прямоугольник) перекрывает нижние скруглённые углы, делая их прямыми.
- Высота нижнего прямоугольника должна быть достаточной, чтобы перекрыть радиус скругления (в примере 10 пикселей — это больше, чем радиус 20? Нет, здесь нужно подобрать высоту, но лучше использовать
cornerRadiusс запасом).
Недостатки этого подхода:
- Он менее гибкий: если фон не сплошной (например, градиент или изображение), наложение будет заметно.
- Требуется точная настройка размеров, что неудобно при динамическом контенте.
- Не работает с прозрачными фонами.
Этот метод подходит только для простых случаев с однотонным фоном. Для продакшена я рекомендую использовать первый подход с кастомной формой — он надёжнее и красивее.