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

如何让UIKit自定义折线图的渐变遮罩路径不闭合于图表底部?

解决UIKit折线图渐变仅显示在折线下方(不闭合到视图底部)的问题

问题核心

当前实现里,渐变遮罩的路径直接把折线连到了视图左下角和右下角,导致渐变覆盖了整个视图底部区域。要让渐变仅终止于最后一个数据点下方的X轴位置,需要拆分折线路径和渐变填充路径:折线路径只连接数据点,渐变填充路径则从最后一个数据点垂直下到X轴,再回到第一个数据点的X轴位置,形成闭合区域。

修改后的完整代码

class LineChartView: UIView {
    var dataPoints: [CGFloat] = [] // 图表数据点
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard !dataPoints.isEmpty else { return }
        
        let horizontalGap = rect.width / CGFloat(dataPoints.count - 1)
        
        // 1. 创建折线专用路径(仅连接数据点)
        let linePath = UIBezierPath()
        let firstPoint = CGPoint(x: 0, y: dataPoints[0])
        linePath.move(to: firstPoint)
        
        for i in 1..<dataPoints.count {
            let nextPoint = CGPoint(x: CGFloat(i) * horizontalGap, y: dataPoints[i])
            linePath.addLine(to: nextPoint)
        }
        
        // 2. 创建渐变填充专用路径(折线下方到X轴的区域)
        let fillPath = UIBezierPath()
        fillPath.append(linePath)
        // 从最后一个数据点垂直下到X轴
        let lastDataPoint = CGPoint(x: rect.width, y: dataPoints.last!)
        fillPath.addLine(to: CGPoint(x: lastDataPoint.x, y: rect.height))
        // 从X轴右侧回到左侧
        fillPath.addLine(to: CGPoint(x: 0, y: rect.height))
        // 回到第一个数据点,闭合路径
        fillPath.close()
        
        // 配置渐变层
        let customBlueColor = UIColor.blue.withAlphaComponent(0.5)
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = rect
        gradientLayer.colors = [customBlueColor.cgColor, UIColor.clear.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
        
        // 渐变遮罩使用填充路径
        let gradientMaskLayer = CAShapeLayer()
        gradientMaskLayer.path = fillPath.cgPath
        gradientLayer.mask = gradientMaskLayer
        layer.addSublayer(gradientLayer)
        
        // 配置折线图层(使用折线专用路径)
        let lineLayer = CAShapeLayer()
        lineLayer.fillColor = UIColor.clear.cgColor
        lineLayer.strokeColor = UIColor.blue.cgColor
        lineLayer.lineWidth = 3.0
        lineLayer.lineCap = .round
        lineLayer.lineJoin = .round
        lineLayer.path = linePath.cgPath
        layer.addSublayer(lineLayer)
        
        // 绘制X轴
        let xAxisLayer = CAShapeLayer()
        xAxisLayer.strokeColor = UIColor.black.cgColor
        xAxisLayer.lineWidth = 3.0
        let xAxisPath = UIBezierPath()
        xAxisPath.move(to: CGPoint(x: 0, y: bounds.height))
        xAxisPath.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
        xAxisLayer.path = xAxisPath.cgPath
        layer.addSublayer(xAxisLayer)
        
        // 绘制Y轴
        let yAxisLayer = CAShapeLayer()
        yAxisLayer.strokeColor = UIColor.black.cgColor
        yAxisLayer.lineWidth = 3.0
        let yAxisPath = UIBezierPath()
        yAxisPath.move(to: CGPoint(x: 0, y: 0))
        yAxisPath.addLine(to: CGPoint(x: 0, y: bounds.height))
        yAxisLayer.path = yAxisPath.cgPath
        layer.addSublayer(yAxisLayer)
    }
}

视图控制器使用代码(无修改)

class ViewController: UIViewController {
    let randomNumbers: [CGFloat] = Array(repeating: 0.0, count: 20).map { _ in CGFloat.random(in: 1..<200) }
    private lazy var lineChartView: LineChartView = {
        let view = LineChartView()
        view.dataPoints = randomNumbers
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view.addSubview(lineChartView)
        configure()
    }
    
    private func configure() {
        NSLayoutConstraint.activate([
            lineChartView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            lineChartView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            lineChartView.heightAnchor.constraint(equalToConstant: 300),
            lineChartView.widthAnchor.constraint(equalToConstant: 400)
        ])
    }
}

修改说明

  • 拆分了linePath和fillPath:折线图层使用仅连接数据点的linePath,保证线条只到最后一个数据点;渐变遮罩使用fillPath,该路径仅闭合折线下方到X轴的区域。
  • 修复了原循环中重复添加第一个点的问题,循环从1开始。
  • 添加了空数据判断,避免数组越界。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 18:44:56