如何用UIBezierPath和CAShapeLayer绘制多颜色路径线条?
为平滑贝塞尔曲线实现沿路径的多色线条
问题概述
需要绘制一条沿路径分布多种颜色的平滑曲线(示例图:多色线条),当前使用UIBezierPath创建带曲线的路径,通过CAShapeLayer渲染,但无法实现多色线条。尝试过CAGradientLayer配合CAShapeLayer遮罩的方案,但颜色会垂直偏移,无法跟随路径走向,希望保留addCurve的平滑效果,寻求无需依赖CAGradientLayer背景的实现方式。
解决方案
方法1:分段创建CAShapeLayer,为每段设置单独颜色
由于路径由多个贝塞尔曲线段组成(每个addCurve对应一段),可以将每一段单独提取为UIBezierPath,为每个路径创建独立的CAShapeLayer并设置不同颜色,完全避免渐变偏移问题。
修改后的完整代码:
import UIKit struct CurvedSegment { var controlPoint1: CGPoint var controlPoint2: CGPoint } class CurveAlgorithm { static let shared = CurveAlgorithm() public func controlPointsFrom(points: [CGPoint]) -> [CurvedSegment] { var result: [CurvedSegment] = [] let delta: CGFloat = 0.3 // 用于计算临时控制点的参数 // 计算临时控制点,使贝塞尔线段初始呈直线状态 for i in 1..<points.count { let A = points[i-1] let B = points[i] let controlPoint1 = CGPoint(x: A.x + delta*(B.x-A.x), y: A.y + delta*(B.y - A.y)) let controlPoint2 = CGPoint(x: B.x - delta*(B.x-A.x), y: B.y - delta*(B.y - A.y)) let curvedSegment = CurvedSegment(controlPoint1: controlPoint1, controlPoint2: controlPoint2) result.append(curvedSegment) } // 优化控制点,使曲线过渡更平滑 for i in 1..<points.count-1 { let M = result[i-1].controlPoint2 let N = result[i].controlPoint1 let A = points[i] // 计算M关于A的对称点 let MM = CGPoint(x: 2 * A.x - M.x, y: 2 * A.y - M.y) // 计算N关于A的对称点 let NN = CGPoint(x: 2 * A.x - N.x, y: 2 * A.y - N.y) result[i].controlPoint1 = CGPoint(x: (MM.x + N.x)/2, y: (MM.y + N.y)/2) result[i-1].controlPoint2 = CGPoint(x: (NN.x + M.x)/2, y: (NN.y + M.y)/2) } return result } // 生成完整的平滑路径 func createCurvedPath(_ dataPoints: [CGPoint]) -> UIBezierPath? { guard !dataPoints.isEmpty else { return nil } let path = UIBezierPath() path.move(to: dataPoints[0]) let curveSegments = controlPointsFrom(points: dataPoints) for i in 1..<dataPoints.count { path.addCurve(to: dataPoints[i], controlPoint1: curveSegments[i-1].controlPoint1, controlPoint2: curveSegments[i-1].controlPoint2) } return path } // 生成单个曲线段的路径 func createSingleCurveSegmentPath(from startPoint: CGPoint, to endPoint: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint) -> UIBezierPath { let path = UIBezierPath() path.move(to: startPoint) path.addCurve(to: endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2) return path } } // 修改后的多色线条绘制函数 func createMultiColoredLineDrawing(points: [CGPoint], colors: [CGColor], lineWidth: CGFloat) { guard points.count >= 2, colors.count == points.count - 1 else { // 颜色数量需与曲线段数量一致(n个点对应n-1段) return } let curveSegments = CurveAlgorithm.shared.controlPointsFrom(points: points) for i in 0..<curveSegments.count { let startPoint = points[i] let endPoint = points[i+1] let segmentPath = CurveAlgorithm.shared.createSingleCurveSegmentPath(from: startPoint, to: endPoint, controlPoint1: curveSegments[i].controlPoint1, controlPoint2: curveSegments[i].controlPoint2) let shapeLayer = CAShapeLayer() shapeLayer.path = segmentPath.cgPath shapeLayer.strokeColor = colors[i] shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = lineWidth shapeLayer.lineCap = .round // 可选,让线段端点更平滑 self.layer.addSublayer(shapeLayer) } }
方法2:使用Core Graphics在draw(_:)方法中分段着色
如果不需要图层动画,可以在自定义UIView的draw(_:)方法中,用Core Graphics逐段绘制并设置不同颜色,代码更简洁:
class MultiColoredCurveView: UIView { var points: [CGPoint] = [] var colors: [UIColor] = [] var lineWidth: CGFloat = 2.0 override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext(), points.count >= 2, colors.count == points.count - 1 else { return } let curveSegments = CurveAlgorithm.shared.controlPointsFrom(points: points) for i in 0..<curveSegments.count { context.setStrokeColor(colors[i].cgColor) context.setLineWidth(lineWidth) context.setLineCap(.round) let startPoint = points[i] let endPoint = points[i+1] let cp1 = curveSegments[i].controlPoint1 let cp2 = curveSegments[i].controlPoint2 context.move(to: startPoint) context.addCurve(to: endPoint, control1: cp1, control2: cp2) context.strokePath() } } } // 使用示例 let curveView = MultiColoredCurveView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)) curveView.points = [CGPoint(x: 50, y: 250), CGPoint(x: 100, y: 100), CGPoint(x: 200, y: 150), CGPoint(x: 250, y: 50)] curveView.colors = [.red, .green, .blue] curveView.lineWidth = 4.0
方案说明
- 方法1通过多个
CAShapeLayer实现,保留了Core Animation的动画支持,每个线段独立控制颜色,完全跟随路径走向,无偏移问题。 - 方法2直接用Core Graphics绘制,适合不需要图层动画的场景,性能更优,同样能保证颜色沿路径分布。
- 两种方案都完整保留了
UIBezierPath.addCurve带来的平滑曲线效果,完全满足需求。
内容的提问来源于stack exchange,提问作者cleanrun
相关产品推荐
相关产品推荐

