UIBezierPath设置不同填充色失效 实现登山坡度颜色编码图表问题
坡度分段着色图表实现问题及解决方案
我的目标是实现如下所示的图表,不同颜色代表每个路段的坡度/梯度百分比。
问题描述
我之前的代码已经完成了“山脉”轮廓的绘制,现在进一步优化,尝试创建一组代表各颜色段的“区块”,但遇到了颜色显示异常的问题,最初使用的代码如下:
let myBezier = UIBezierPath() let nextPt = fillSlopes(at: idx) if idx > 0 { let prevPt = fillSlopes(at: idx - 1) myBezier.move(to: CGPoint(x: prevPt.x, y: height)) myBezier.addLine(to: CGPoint(x: prevPt.x, y: prevPt.y)) myBezier.addLine(to: CGPoint(x: nextPt.x, y: nextPt.y)) myBezier.addLine(to: CGPoint(x: nextPt.x, y: height)) myBezier.addLine(to: CGPoint(x: prevPt.x, y: height)) myBezier.close() // 测试代码,实际代码会对比数组中存储的当前坡度百分比,根据数值切换对应颜色 if idx % 2 == 0 { UIColor.systemRed.setFill() myBezier.fill() } else { UIColor.systemBlue.setFill() myBezier.fill() } }
除了颜色异常问题,我还发现绘制大量小区块会消耗很多CPU资源,希望能得到更优的实现方案。
问题根因
经过排查,问题出在UIBezierPath实例的声明位置:最初将let myBezier = UIBezierPath()写在了for循环外部,所有循环迭代共用同一个路径实例,最终导致[UIBezierPath]数组数据错乱,颜色显示异常。
错误写法示例:
func xxx { let myBezier = UIBezierPath() // 此处为错误写法,所有循环共用同一路径实例 for idx in slopeBars.indices { // 逻辑处理 } }
修复方案
将UIBezierPath实例的声明移至for循环内部,每次迭代生成独立的路径实例,同时将路径和对应颜色分开存储,最后统一绘制,即可解决颜色异常问题,同时也能优化部分性能:
func xxx { var slopeBars = [UIBezierPath]() var slopeBarColorArray = [UIColor]() // 第一步:生成所有路径和对应颜色 for idx in slopeBars.indices { let myBezier = UIBezierPath() // 移至循环内部,每次生成独立路径 let nextPt = fillSlopes(at: idx) if idx > 0 { let prevPt = fillSlopes(at: idx - 1) myBezier.move(to: CGPoint(x: prevPt.x, y: height)) myBezier.addLine(to: CGPoint(x: prevPt.x, y: prevPt.y)) myBezier.addLine(to: CGPoint(x: nextPt.x, y: nextPt.y)) myBezier.addLine(to: CGPoint(x: nextPt.x, y: height)) myBezier.addLine(to: CGPoint(x: prevPt.x, y: height)) myBezier.close() slopeBars.append(myBezier) // 根据坡度值存储对应颜色 if gradient < 0.0 { slopeBarColorArray.append(UIColor(cgColor: Consts.elevChart.slope0_0)) } else if gradient < 4.0 { slopeBarColorArray.append(UIColor(cgColor: Consts.elevChart.slope0_4)) } } } // 第二步:统一绘制所有路径(原代码将这部分写在循环内部会导致重复绘制,大幅浪费性能,需移到外层) for i in 0..<slopeBars.count { guard !slopeBarColorArray.isEmpty else { break } slopeBarColorArray[i].setStroke() slopeBarColorArray[i].setFill() slopeBars[i].stroke() slopeBars[i].fill() } }
最终实现效果如下:
额外性能优化建议
如果坡度段数量非常多,还可以通过以下方式进一步降低CPU消耗:
- 合并相邻的同色路径,减少填充和描边的调用次数
- 静态场景下将绘制结果预渲染为UIImage缓存,后续直接展示缓存图片,不需要每次重绘
- 改用CGGradient配合路径裁剪实现分段着色,不需要拆分大量小路径,性能提升更明显
内容的提问来源于stack exchange,提问作者app4g
相关产品推荐
相关产品推荐

