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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 23:17:51