新增音频数据时Animate CAShapeLayer实现iOS语音备忘录滚动波形卡顿优化问询
性能瓶颈分析
首先你的现有实现存在三个核心性能问题,是卡顿的主要原因:
- 每次新增波形条都复用全量历史路径,路径长度随录音时长线性增长,CAShapeLayer每次渲染都要计算整条路径的像素,开销越来越大
- 重载UIView的
draw(_ rect:)方法触发的是CPU软渲染,本身效率远低于Core Animation的硬件加速渲染 - 每次重绘全路径+平移整个图层的组合操作,同时占用大量CPU和GPU资源,帧率会随录音时间增加持续下降
最优实现方案(双图层接力方案,性能开销恒定)
这个方案不需要维护全量路径,也不需要用滚动视图,全程靠硬件加速渲染,无论录音多久帧率都稳定在60帧:
- 首先配置全局参数和基础图层
// 全局配置 private let barSpacing: CGFloat = 5 // 每条波形占的宽度,和你原有步长一致 private let barWidth: CGFloat = 3 // 波形条自身宽度,留2px间距 private let containerLayer = CALayer() // 父容器,做边界裁剪 private var currentLayer = CAShapeLayer() // 当前在屏幕展示的图层 private var nextLayer = CAShapeLayer() // 待移入屏幕的图层 private var currentDrawX: CGFloat = 0 // 当前在nextLayer上的绘制坐标
- 初始化图层配置(在View的init方法里调用一次即可,不要放在draw方法里)
private func setupLayers() { containerLayer.frame = self.bounds containerLayer.masksToBounds = true self.layer.addSublayer(containerLayer) currentLayer.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height) nextLayer.frame = CGRect(x: self.bounds.width, y: 0, width: self.bounds.width, height: self.bounds.height) [currentLayer, nextLayer].forEach { layer in layer.strokeColor = UIColor.systemBlue.cgColor layer.lineWidth = barWidth containerLayer.addSublayer(layer) } }
- 新增波形数据时的处理(每次新的RMS值生成时调用,不要放在draw方法里)
func addNewWave(_ amplitude: Int) { let waveHeight = max(2, min(CGFloat(amplitude), self.bounds.height/2 - 2)) let centerY = self.bounds.height/2 // 只在nextLayer的当前绘制位置加一条线,不用修改历史路径 let path = UIBezierPath() path.move(to: CGPoint(x: currentDrawX, y: centerY - waveHeight)) path.addLine(to: CGPoint(x: currentDrawX, y: centerY + waveHeight)) let currentPath = nextLayer.path ?? CGPath.empty let mutablePath = CGPathCreateMutableCopy(currentPath)! CGPathAddPath(mutablePath, nil, path.cgPath) nextLayer.path = mutablePath currentDrawX += barSpacing // 两个图层整体左移5px,用Core Animation隐式动画保证流畅 CATransaction.begin() CATransaction.setAnimationDuration(0.05) CATransaction.setDisableActions(false) currentLayer.position.x -= barSpacing nextLayer.position.x -= barSpacing CATransaction.commit() // 当currentLayer完全移出屏幕时,循环替换图层 if currentLayer.frame.maxX <= 0 { currentLayer.removeFromSuperlayer() currentLayer = nextLayer nextLayer = CAShapeLayer() nextLayer.frame = CGRect(x: self.bounds.width, y: 0, width: self.bounds.width, height: self.bounds.height) nextLayer.strokeColor = UIColor.systemBlue.cgColor nextLayer.lineWidth = barWidth containerLayer.addSublayer(nextLayer) currentDrawX = 0 } }
额外优化点
- 不需要保留全量
waveform数组的话可以只保留最近1000条数据做回溯,进一步降低内存占用 - 如果需要支持拖动查看历史波形,可以搭配UIScrollView做图层回收复用,类似UITableView的Cell复用机制,性能同样稳定
- 禁止重载UIView的
draw(_ rect)方法,所有图层操作都交给Core Animation处理,避免CPU软渲染
内容的提问来源于stack exchange,提问作者Amit Singh
相关产品推荐
相关产品推荐

