You need to enable JavaScript to run this app. 新增音频数据时Animate CAShapeLayer实现iOS语音备忘录滚动波形卡顿优化问询
优惠活动
大模型
产品
解决方案
定价
更多

新增音频数据时Animate CAShapeLayer实现iOS语音备忘录滚动波形卡顿优化问询

性能瓶颈分析

首先你的现有实现存在三个核心性能问题,是卡顿的主要原因:

  • 每次新增波形条都复用全量历史路径,路径长度随录音时长线性增长,CAShapeLayer每次渲染都要计算整条路径的像素,开销越来越大
  • 重载UIView的draw(_ rect:)方法触发的是CPU软渲染,本身效率远低于Core Animation的硬件加速渲染
  • 每次重绘全路径+平移整个图层的组合操作,同时占用大量CPU和GPU资源,帧率会随录音时间增加持续下降

最优实现方案(双图层接力方案,性能开销恒定)

这个方案不需要维护全量路径,也不需要用滚动视图,全程靠硬件加速渲染,无论录音多久帧率都稳定在60帧:

  1. 首先配置全局参数和基础图层
// 全局配置
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上的绘制坐标
  1. 初始化图层配置(在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)
    }
}
  1. 新增波形数据时的处理(每次新的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.19 15:36:03