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

