SwiftUI如何实现钟表样式的圆形裁剪效果?
问题分析
你的自定义Shape代码存在几个关键问题:
- 硬编码了半径
r=2和圆心横坐标s=2,完全没利用传入的rect参数,导致不管frame设多大,图形都固定在极小区域 - 角度计算逻辑错误:Core Graphics用弧度而非角度,你直接用
360 * trim是角度值,必须转成弧度才能计算 - 自定义的数学公式逻辑混乱,完全偏离了"根据角度计算圆周上点"的正确方法
更简便的实现方案
没必要写复杂的自定义Shape,用SwiftUI内置的工具就能轻松实现需求,以下是两种靠谱方案:
方案1:直接绘制扇形(最直观)
直接根据trim值计算结束角度,绘制从顶部起始的扇形区域:
struct TimerArc: Shape { var trim: Double // 范围0~1,对应0%~100% func path(in rect: CGRect) -> Path { let center = CGPoint(x: rect.midX, y: rect.midY) let radius = min(rect.width, rect.height) / 2 // 起始角度设为顶部:默认0弧度是右侧,逆时针转90度(-π/2)到顶部 let startAngle = Angle(radians: -Double.pi / 2) // 结束角度 = 起始角度 + trim对应的总弧度(2π是一圈) let endAngle = startAngle + Angle(radians: 2 * Double.pi * trim) var path = Path() path.move(to: center) path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) path.closeSubpath() return path } } // 使用示例 struct ContentView: View { var body: some View { VStack { TimerArc(trim: 0.5) .fill(Color.accentColor) .frame(width: 100, height: 100) // 也可以做描边样式 TimerArc(trim: 0.75) .stroke(Color.red, lineWidth: 4) .frame(width: 100, height: 100) } } }
方案2:利用内置trim+旋转(最简洁)
SwiftUI的Circle本身支持trim修饰符,配合旋转让起始点从顶部开始:
struct ContentView: View { var body: some View { VStack { Circle() .trim(from: 0, to: 0.5) .fill(Color.accentColor) // 旋转-90度,让trim的起始点从顶部开始(默认trim从右侧开始) .rotationEffect(Angle(degrees: -90)) .frame(width: 100, height: 100) } } }
原代码修复(如果坚持自定义Shape)
要是你想基于自己的思路调整,需要修正这几点:
- 去掉硬编码的r和s,从rect里取正确的圆心和半径
- 把角度转成弧度计算
- 用三角函数正确计算圆周上的点:
struct ClockShape: Shape { var trim: Double func properPoint(_ rect: CGRect) -> CGPoint { let center = CGPoint(x: rect.midX, y: rect.midY) let radius = min(rect.width, rect.height) / 2 // 起始角度是顶部(-π/2),加上trim对应的弧度 let angle = -Double.pi / 2 + 2 * Double.pi * trim let x = center.x + CGFloat(cos(angle)) * radius let y = center.y + CGFloat(sin(angle)) * radius return CGPoint(x: x, y: y) } func path(in rect: CGRect) -> Path { var path = Path() let center = CGPoint(x: rect.midX, y: rect.midY) let point = properPoint(rect) path.move(to: CGPoint(x: rect.midX, y: rect.minY)) // 顶部起始点 path.addLine(to: center) // 连接到圆心 path.addLine(to: point) // 连接到圆周上的目标点 path.closeSubpath() // 自动闭合回起始点 return path } }
修复后就能生成从顶部到指定角度的扇形区域,符合你的需求。
内容的提问来源于stack exchange,提问作者green8
相关产品推荐
相关产品推荐

