Я работаю со SwiftUI, и мне нужно реализовать собственную форму, имитирующую определенный дизайн. Форма представляет собой прямоугольник, но с характерными особенностями: верхние углы закруглены, а нижний край напоминает ряд скругленных треугольников, что придает ему вид рваной бумаги.
Может ли кто-нибудь подсказать мне, как создать эту конкретную форму, или предложить лучший подход для достижения эффекта рваной бумаги на нижнем крае прямоугольника в SwiftUI?
Вот что я пробовал до сих пор:
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.size.width
let height = rect.size.height
let bottomLeft = CGPoint(x: 0, y: height)
let bottomRight = CGPoint(x: width, y: height)
let topLeft = CGPoint(x: 0, y: 0)
let topRight = CGPoint(x: width, y: 0)
path.move(to: bottomRight)
path.addLine(to: CGPoint(x: bottomRight.x, y: topRight.y))
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
let triangleWidth: CGFloat = 11
let triangleHeight: CGFloat = 6
var x: CGFloat = 0
while x <= rect.width {
let startX = x
let endX = x + triangleWidth
let midX = (startX + endX) / 2
path.move(to: CGPoint(x: startX, y: rect.maxY))
path.addLine(to: CGPoint(x: midX, y: rect.maxY - triangleHeight))
path.addLine(to: CGPoint(x: endX, y: rect.maxY))
x += triangleWidth
}
return path
}
}
Я параметризую эту форму радиусом верхних углов, угловым радиусом треугольников и размером треугольников.
Данному rect
может не соответствовать целое число таких треугольников. Непонятно, как в этом случае должна выглядеть фигура, поэтому я буду считать ширину треугольника минимальной шириной. Фактические треугольники могут быть шире, чтобы вместить целое число треугольников в заданный rect
.
Вот код. Основная идея состоит в том, чтобы использовать addArc(tangent1End:tangent2End:radius:), чтобы каждая линия, которую мы рисуем, имела закругленный угол. См. также объяснение в комментариях.
struct CustomShape: Shape {
let topCornersRadius: CGFloat
let trianglesCornerRadius: CGFloat
let triangleHeight: CGFloat
let minTriangleWidth: CGFloat
func path(in rect: CGRect) -> Path {
Path { path in
// calculate the minimum width of the triangles,
// such that it is at least minTriangleWidth, and
// rect.width fits an integer number of such triangles
let triangleCount = Int(rect.width / minTriangleWidth)
let actualTriangleWidth = rect.width / CGFloat(triangleCount)
// start from the top left
path.move(to: .init(x: rect.minX + topCornersRadius, y: rect.minY))
// draw the rounded corner for the top left corner
path.addArc(
tangent1End: rect.origin,
tangent2End: .init(x: rect.minX, y: rect.minX + topCornersRadius),
radius: topCornersRadius
)
// draw the left side of the shape
var tangent1End = CGPoint(x: rect.minX, y: rect.maxY)
var tangent2End = tangent1End.applying(.init(translationX: actualTriangleWidth / 2, y: -triangleHeight))
path.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: trianglesCornerRadius / 2)
// draw most of the triangles
// each iteration draws one side of a triangle
// we don't draw the right side of the last triangle in the loop,
// because it will have a different tangent2End
for i in 0..<(2 * triangleCount - 1) {
tangent1End = tangent2End
tangent2End = tangent1End.applying(.init(
translationX: actualTriangleWidth / 2,
y: triangleHeight * (i.isMultiple(of: 2) ? 1 : -1)
))
path.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: trianglesCornerRadius)
}
// draw the right side of the last triangle
path.addArc(tangent1End: tangent2End, tangent2End: .init(x: rect.maxX, y: rect.minY), radius: trianglesCornerRadius / 2)
// draw the right side of the shape
path.addArc(
tangent1End: .init(x: rect.maxX, y: rect.minY),
tangent2End: rect.origin,
radius: topCornersRadius
)
// connects the top right corner of the shape to the top left corner
path.closeSubpath()
}
}
}
Пример вывода: