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

为何Core Animation相较于Core Graphics与UIView存在动画延迟?

自定义UISlider的Core Animation延迟问题

我在iOS设备上实现了一个自定义UISlider,初始状态下两个ThumbSlider位于两端。期望的交互是:拖动ThumbSlider左右滑动时,两者之间的TopView同步伸缩。

  • 当TopView基于UIView实现时,运行完全正常
  • 基于Core Graphics实现时,同样运行流畅
  • 但用Core Animation实现TopView时,ThumbSlider和动画之间出现明显延迟,完全不符合预期

我能理解UIView可能对此类操作做了优化,但搞不懂为什么Core Animation反而比Core Graphics慢。我的问题是:

  1. 为什么Core Animation的绘图速度比UIView慢?
  2. 为什么基于CPU的Core Graphics会比Core Animation更快?

自定义Slider代码实现

final class EditSliderBar: UIControl {
  enum Constants {
    static let sliderWidth: CGFloat = 16
  }
  
  // MARK: - UI Components
  private let lowerThumbSlider = EditSlider(tintColor: .systemYellow)
  private let upperThumbSlider = EditSlider(tintColor: .systemYellow)
  private let topView = UIView()
  private let topLayer = CALayer()
  weak var currentHighlightedThumbSlider: EditSlider?
  
  private var previousLocation: CGPoint = .zero
  private var minimumValue: Double = 0
  private var maximumValue: Double = 100
  
  ...

  private(set) var lowerValue: Double = 0.0 {
    didSet {
      updateSliderFrame(lowerThumbSlider)
    }
  }
  

  private(set) var upperValue: Double = 100 {
    didSet {
      updateSliderFrame(upperThumbSlider)
    }
  }

  ...

  
  // MARK: - UIControl
  override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    let location = touch.location(in: self)
    defer { previousLocation = location }

    if lowerSlider.contain(point: location) {
      lowerSlider.isHightlighted = true
      currentHighlightedSlider = lowerSlider
    } else if upperSlider.contain(point: location) {
      upperSlider.isHightlighted = true
      currentHighlightedSlider = upperSlider
    }
    
    return lowerSlider.isHightlighted || upperSlider.isHightlighted
  }
  
  override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    let location = touch.location(in: self)
    defer { previousLocation = location }
    
    guard let slider = currentHighlightedThumbSlider else { return false }
    
    let sliderDeltaValue = deltaValue(from: previousLocation, to: location)
    
    if slider === lowerThumbSlider {
      self.lowerValue = updatedLowerValue(moved: sliderDeltaValue)
    } else if slider === upperThumbSlider {
      self.upperValue = updatedUpperValue(moved: sliderDeltaValue)
    }
    sendActions(for: .valueChanged)
    return true
  }
  
  override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
    ...
  }
  
  // MARK: - LayoutSubviews
  override func layoutSubviews() {
    super.layoutSubviews()
    updateSliderFrames()
  }
  
  // MARK: - Draw
  override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    
    let maskedRect = CGRect(
      x: lowerSlider.center.x,
      y: 0,
      width: upperThumbSlider.center.x - lowerThumbSlider.center.x,
      height: 5
    )
    
    context.setFillColor(UIColor.systemYellow.cgColor)
    context.fill(maskedRect)
  }
}

// MARK: - Private Methods
private extension EditSliderBar {
  func updateSliderFrames() {
    updateSliderFrame(lowerThumbSlider)
    updateSliderFrame(upperThumbSlider)
  }
  
  func updateSliderFrame(_ slider: EditSlider) {
    let width = Constants.sliderWidth
    
    let leading = slider === lowerThumbSlider ? leading(of: lowerValue) : leading(of: upperValue)
    
    slider.frame = CGRect(
      x: leading,
      y: 0,
      width: width,
      height: bounds.height
    )
    setNeedsDisplay()
    
    /// Core Animation
    updateTopLayer()
    /// Core Graphics
    setNeedsDisplay()
    /// UIView
    updateTopView()
  }

  // Core Animation
  func updateTopLayer() {
    topLayer.frame = CGRect(
      x: lowerThumbSlider.center.x,
      y: 0,
      width: upperThumbSlider.frame.maxX - lowerThumbSlider.center.x,
      height: 5
    )
    topLayer.backgroundColor = UIColor.systemYellow.cgColor
  }
  
  // UIView
  func updateTopView() {
    topView.frame = CGRect(
      x: lowerSlider.center.x,
      y: 0,
      width: upperThumbSlider.frame.maxX - lowerThumbSlider.center.x,
      height: 5
    )
    topView.backgroundColor = UIColor.systemYellow
  }
  
  func updatedLowerValue(moved delta: Double) -> Double {
    return (lowerValue + delta).bound(lower: minimumValue, upper: upperValue - gapBetweenSliders)
  }
  
  func updatedUpperValue(moved delta: Double) -> Double {
    return (upperValue + delta).bound(lower: lowerValue + gapBetweenSliders, upper: maximumValue)
  }

  func deltaValue(from previous: CGPoint, to current: CGPoint) -> Double {
    let deltaLocation = Double(current.x - previous.x)
    
    return (maximumValue - minimumValue) * deltaLocation / Double(totalLength)
  }
  
  func leading(of value: Double) -> Double {
    return totalLength * value / maximumValue
  }
}

问题解答

1. 为什么Core Animation看起来比UIView慢?

不是Core Animation本身慢,是你当前的实现方式有问题。UIView在处理交互(比如手势拖动)时,会自动禁用隐式动画——它内部会通过事务设置kCATransactionDisableActions,确保属性更新立即生效,和手势刷新周期完全同步。

但你直接修改CALayer的frame时,Core Animation默认会给属性变化加上0.25秒的隐式过渡动画,这个动画的插值过程会导致图层位置滞后于ThumbSlider的实时位置,视觉上就出现了延迟。

2. 为什么Core Graphics比Core Animation更快?

你用Core Graphics是在draw(_:)方法里直接绘制,每次调用setNeedsDisplay()后,系统会在下一次屏幕刷新前完成CPU绘制,然后直接提交到GPU显示,整个过程是同步的,没有额外的动画过渡,所以和ThumbSlider的拖动完全同步,不会有延迟感。


修复方案

要让Core Animation实现和另外两种方式一样流畅,只需要在更新图层属性时禁用隐式动画:

// 修改updateTopLayer方法
func updateTopLayer() {
    CATransaction.begin()
    CATransaction.setDisableActions(true) // 禁用隐式动画
    topLayer.frame = CGRect(
      x: lowerThumbSlider.center.x,
      y: 0,
      width: upperThumbSlider.frame.maxX - lowerThumbSlider.center.x,
      height: 5
    )
    topLayer.backgroundColor = UIColor.systemYellow.cgColor
    CATransaction.commit()
}

这样修改后,图层的frame会立即更新,和ThumbSlider的拖动保持同步,延迟问题就会消失。


内容的提问来源于stack exchange,提问作者정석영

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:20:54