You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

要是你想基于自己的思路调整,需要修正这几点:

  1. 去掉硬编码的r和s,从rect里取正确的圆心和半径
  2. 把角度转成弧度计算
  3. 用三角函数正确计算圆周上的点:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 20:05:35