为何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慢。我的问题是:
- 为什么Core Animation的绘图速度比UIView慢?
- 为什么基于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,提问作者정석영
相关产品推荐
相关产品推荐

