SwiftUI自定义形状:顶部圆角+底部撕纸锯齿效果实现指引
SwiftUI 实现顶部圆角+底部撕纸锯齿效果的自定义Shape
我正在用SwiftUI开发,需要实现一个自定义Shape:整体是矩形,顶部带圆角,底部边缘是一排带圆角的三角形,模拟撕纸效果。目前尝试的代码如下,但效果不符合预期:
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 } }
现有代码问题分析
你的代码存在两个核心问题:
- 先绘制了完整的矩形底部边缘,之后又叠加绘制三角形,导致路径重叠混乱,Shape的填充/描边会出现错误。
- 没有实现顶部的圆角效果,且底部的三角形是尖角,不符合“圆角三角形”的撕纸需求。
优化实现方案
正确的做法是构建连续的路径,从顶部圆角开始,沿着左侧边缘到左下角,然后逐个绘制带圆角的底部锯齿,最后回到右上角闭合路径。以下是优化后的实现:
struct TornPaperShape: Shape { // 可配置参数,方便根据设计调整 var topCornerRadius: CGFloat = 12 var toothWidth: CGFloat = 11 var toothHeight: CGFloat = 6 var toothCornerRadius: CGFloat = 2 func path(in rect: CGRect) -> Path { var path = Path() let width = rect.width let height = rect.height // 1. 绘制顶部圆角:从右上角到左上角 path.move(to: CGPoint(x: width, y: topCornerRadius)) path.addArc(center: CGPoint(x: width - topCornerRadius, y: topCornerRadius), radius: topCornerRadius, startAngle: .zero, endAngle: .degrees(90), clockwise: false) path.addLine(to: CGPoint(x: topCornerRadius, y: 0)) path.addArc(center: CGPoint(x: topCornerRadius, y: topCornerRadius), radius: topCornerRadius, startAngle: .degrees(90), endAngle: .degrees(180), clockwise: false) path.addLine(to: CGPoint(x: 0, y: height - toothHeight)) // 2. 绘制底部圆角锯齿 var currentX: CGFloat = 0 while currentX < width { let nextX = min(currentX + toothWidth, width) let midX = currentX + (nextX - currentX) / 2 // 从当前点到锯齿底部(带圆角过渡) path.addArc(center: CGPoint(x: currentX, y: height), radius: toothCornerRadius, startAngle: .degrees(180), endAngle: .degrees(270), clockwise: false) path.addLine(to: CGPoint(x: midX, y: height - toothHeight)) path.addArc(center: CGPoint(x: nextX, y: height), radius: toothCornerRadius, startAngle: .degrees(90), endAngle: .degrees(0), clockwise: false) currentX = nextX } // 3. 闭合路径,回到起点 path.addLine(to: CGPoint(x: width, y: height)) path.addLine(to: CGPoint(x: width, y: topCornerRadius)) return path } }
关键细节说明
- 所有参数都做成可配置的,你可以根据设计需求调整圆角大小、锯齿尺寸。
- 路径全程连续绘制,没有多余的
move(to:)操作,避免路径重叠导致的渲染错误。 - 底部锯齿的圆角通过
addArc实现,模拟真实撕纸的圆润边缘。 - 顶部圆角用标准圆弧绘制,符合常规圆角矩形的视觉效果。
使用示例
你可以像普通Shape一样直接使用:
struct ContentView: View { var body: some View { TornPaperShape() .fill(Color.white) .frame(width: 300, height: 200) .shadow(color: .gray.opacity(0.3), radius: 4, x: 0, y: 2) } }
内容的提问来源于stack exchange,提问作者Danila Belyi
相关产品推荐
相关产品推荐

