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
相关产品推荐
相关产品推荐

