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

SwiftUI中如何平滑绘制路径以避免尖锐转角?

如何在SwiftUI绘图应用中平滑自由绘制路径,消除尖锐转角

我正在开发一款SwiftUI绘图应用,支持用户绘制自由形态线条。目前用二次贝塞尔曲线(quadratic Bézier curve)绘制路径,但用户突然改变方向时仍会出现尖锐拐角,希望进一步平滑路径,让不规则绘制也能呈现连续流畅的效果。

当前实现代码

@State private var dragPath: [CGPoint] = []
@State private var isCreated = false // 补充原代码中用到的变量

ZStack {
    Path { path in
        guard dragPath.count >= 2 else { return }
        
        path.move(to: dragPath[0])
        
        for i in 1..<dragPath.count {
            let previousPoint = dragPath[i - 1]
            let currentPoint = dragPath[i]
            
            let controlPoint = CGPoint(
                x: (previousPoint.x + currentPoint.x) / 2,
                y: (previousPoint.y + currentPoint.y) / 2
            )
            
            path.addQuadCurve(to: currentPoint, control: controlPoint)
        }
    }
    .stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
}
.gesture(
    DragGesture(minimumDistance: 0)
        .onChanged { value in
            if !isCreated {
                dragPath.append(value.location)
            }
        }
        .onEnded { _ in isCreated = true }
)

解决方案

1. 改用三次贝塞尔曲线+连续控制点计算

二次贝塞尔仅依赖相邻点中点做控制点,方向突变时无法平滑过渡。三次贝塞尔可通过前后点切线生成连续控制点,让转角更自然:

Path { path in
    guard dragPath.count >= 3 else { 
        // 点数不足时降级为直线绘制
        if dragPath.count == 2 {
            path.move(to: dragPath[0])
            path.addLine(to: dragPath[1])
        }
        return 
    }
    
    path.move(to: dragPath[0])
    
    for i in 1..<dragPath.count - 1 {
        let prevPoint = dragPath[i-1]
        let currPoint = dragPath[i]
        let nextPoint = dragPath[i+1]
        
        // 基于前后点切线计算贝塞尔控制点
        let controlPoint1 = CGPoint(
            x: currPoint.x - (nextPoint.x - prevPoint.x) / 6,
            y: currPoint.y - (nextPoint.y - prevPoint.y) / 6
        )
        let controlPoint2 = CGPoint(
            x: currPoint.x + (nextPoint.x - prevPoint.x) / 6,
            y: currPoint.y + (nextPoint.y - prevPoint.y) / 6
        )
        
        path.addCurve(to: nextPoint, control1: controlPoint1, control2: controlPoint2)
    }
}
.stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))

2. 过滤冗余点+路径简化

用户拖动会产生大量密集点,既影响性能又容易导致尖锐转角。可通过距离阈值过滤过近的点:

// 工具方法:过滤距离过近的点
func filterPoints(_ points: [CGPoint], threshold: CGFloat = 5) -> [CGPoint] {
    guard !points.isEmpty else { return [] }
    var filtered: [CGPoint] = [points[0]]
    for point in points[1...] {
        let last = filtered.last!
        if hypot(point.x - last.x, point.y - last.y) > threshold {
            filtered.append(point)
        }
    }
    return filtered
}

// 在手势回调中使用
.gesture(
    DragGesture(minimumDistance: 0)
        .onChanged { value in
            if !isCreated {
                let newPoint = value.location
                if let last = dragPath.last {
                    if hypot(newPoint.x - last.x, newPoint.y - last.y) > 5 {
                        dragPath.append(newPoint)
                    }
                } else {
                    dragPath.append(newPoint)
                }
            }
        }
        .onEnded { _ in isCreated = true }
)

3. 基于Catmull-Rom样条实现平滑曲线

Catmull-Rom样条专门用于生成连续平滑曲线,通过插值相邻点消除尖锐转角,可转换为贝塞尔曲线实现:

// 工具方法:将点数组转换为Catmull-Rom平滑路径
func catmullRomPath(from points: [CGPoint]) -> Path {
    var path = Path()
    guard points.count >= 2 else { return path }
    
    // 扩展首尾点,保证曲线平滑延伸到端点
    var extendedPoints = points
    extendedPoints.insert(points[0], at: 0)
    extendedPoints.append(points.last!)
    
    path.move(to: points[0])
    
    for i in 1..<extendedPoints.count - 2 {
        let p0 = extendedPoints[i-1]
        let p1 = extendedPoints[i]
        let p2 = extendedPoints[i+1]
        let p3 = extendedPoints[i+2]
        
        // 计算贝塞尔控制点
        let c1 = CGPoint(
            x: p1.x + (p2.x - p0.x) / 6,
            y: p1.y + (p2.y - p0.y) / 6
        )
        let c2 = CGPoint(
            x: p2.x - (p3.x - p1.x) / 6,
            y: p2.y - (p3.y - p1.y) / 6
        )
        
        path.addCurve(to: p2, control1: c1, control2: c2)
    }
    
    return path
}

// 使用方式
Path { path in
    let smoothedPath = catmullRomPath(from: dragPath)
    path.addPath(smoothedPath)
}
.stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))

效果对比

  • 当前效果:路径在方向突变处存在明显尖锐拐角,线条过渡生硬
  • 期望效果:路径整体连续顺滑,即使方向突变也能自然过渡,无明显棱角

内容的提问来源于stack exchange,提问作者HeWhoRemains

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:05:18