将自定义可设置动画的UIView属性与其他动画同步

时间:2018-02-21 12:37:56

标签: ios swift core-animation calayer uiviewanimation

我有一个带有自定义动画属性的UIView子类,该属性与其他视图上的其他(内置)UIView属性一起动画。我遇到的问题与这样一个事实有关:这个自定义属性在动画期间似乎与布局的其余部分略微不同步。

这是一个简单的例子来说明我的意思:

Custom property synchronization issue

使用内置的transform属性为蓝色方块设置动画,而红色方块在另一个(静止)UIView内绘制,并由自定义属性drawFrame设置动画。两个属性的动画匹配,从理论上讲,两个矩形应始终完全垂直对齐。

以下是上述示例的代码。要执行,只需启动一个新的Single View App项目并替换ViewController.swift

的内容
import UIKit

class ViewController: UIViewController {

    let viewA = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
    let viewB = CustomAnimatedPropertyView(frame: CGRect(x: 50, y: 150, width: 200, height: 100))

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(viewA); viewA.backgroundColor = UIColor.blue
        view.addSubview(viewB); viewB.backgroundColor = UIColor.clear
        animate()
    }

    func animate() {
        viewA.transform = CGAffineTransform.identity
        viewB.drawFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
        UIView.animate(withDuration: 2.0, delay: 1.0, options: [], animations: {
            self.viewA.transform = CGAffineTransform.identity.translatedBy(x: 100, y: 0.0)
            self.viewB.drawFrame = CGRect(x: 100, y: 0, width: 100, height: 100)
        })
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { self.animate() }
    }
}

class CustomAnimatedPropertyView: UIView {

    @objc dynamic var drawFrame: CGRect = CGRect.zero {
        didSet {
            guard let layer = layer as? CustomAnimatedPropertyLayer else { fatalError() }
            layer.drawFrame = drawFrame
        }
    }

    override public class var layerClass: AnyClass {
        return CustomAnimatedPropertyLayer.self
    }

    override public func action(for layer: CALayer, forKey event: String) -> CAAction? {
        if event == #keyPath(CustomAnimatedPropertyLayer.drawFrame),
           let action = super.action(for: layer, forKey: #keyPath(backgroundColor)) as? CAAnimation,
           let animation: CABasicAnimation = (action.copy() as? CABasicAnimation) {
            animation.keyPath = #keyPath(CustomAnimatedPropertyLayer.drawFrame)
            animation.fromValue = (layer as! CustomAnimatedPropertyLayer).drawFrame
            animation.toValue = drawFrame
            layer.add(animation, forKey: #keyPath(CustomAnimatedPropertyLayer.drawFrame))
            return animation
        }
        return super.action(for: layer, forKey: event)
    }

    override func draw(_ layer: CALayer, in ctx: CGContext) {
        guard let customLayer = layer as? CustomAnimatedPropertyLayer else { super.draw(layer, in: ctx); return }
        ctx.setFillColor(UIColor.red.cgColor)
        ctx.fill(customLayer.drawFrame)
        usleep(20 * 1000) // <- To exacerbate the synchronization issue
    }
}

class CustomAnimatedPropertyLayer: CALayer {

    @NSManaged var drawFrame: CGRect
    override class func needsDisplay(forKey key: String) -> Bool {
        return super.needsDisplay(forKey: key) || key == #keyPath(drawFrame)
    }
}

我已尝试很多的不同解决方案无济于事...我认为这可以使用CADisplayLink或类似解决,但我更喜欢避免涉及重要代码重构的解决方案。

关于如何解决这个问题的任何想法?提前感谢您的任何答案。

1 个答案:

答案 0 :(得分:1)

我认为你的问题没有通用的解决方案,动画的抽搐来自不同的类型。转换动画应该比标量值上的简单动画更复杂。

但是,我看到以下可能性摆脱了同步问题。我有几个替代建议。

  1. 如果您的两个动画是真正的翻译,您应该表达为翻译:
    1. 最好的方法应该是将两个图层放入一个公共超级图层并移动超级图层。
    2. 或者对两个动画使用相同的动画属性,例如由他们的transformation财产翻译。您应该为两个图层使用通用的基本动画。
  2. 如果确实需要一个动画的自定义动画属性,您应该尝试通过自定义属性表达其他动画。如果两个动画都基于不同的自定义属性,则可以使用动画组,这样可以改善两个动画的同步。 (注意:如果动画基于公共属性,则不能使用组,因为它还会为其他图层上的公共属性设置动画。)
  3. 如果两个动画值都是严格连接的,您可以创建一个显示两个图层内容的组合图层。它只有一个可动画的属性,可以适当地改变它的表示形式。
  4. 不幸的是,我的提议非常普遍,但为了能够提供更具体的途径,我必须更多地了解具体问题。