swiftuikitcabasicanimationcakeyframeanimationcatransaction

How to do linear transition and enlarge Animation By CAKeyframeAnimation?


I want to do a linear animation like this: move the rectangle slowly to center of Screen, and tripled its size while moving.

Here is my wrong code and demo, I can only do animation one by one, but I want to combine this two animation. Also, the end position is not right.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        presentAnimation()
    }

    private lazy var rectView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        view.layer.borderColor = UIColor.blue.cgColor
        return view
    }()
}

extension ViewController {
    private func setupUI() {
        
        view.addSubview(rectView)
        rectView.snp.makeConstraints { 
            $0.left.equalToSuperview().inset(40)
            $0.top.equalToSuperview().inset(100)
            $0.width.equalTo(40)
            $0[![enter image description here][1]][1].height.equalTo(40)
        }
    }
    
    private func presentAnimation() {
        let testEnlarge = CABasicAnimation(keyPath: "transform.scale")
        testEnlarge.fromValue = 1
        testEnlarge.toValue = 3
        testEnlarge.beginTime = CACurrentMediaTime() + 2
        testEnlarge.duration = 1
        testEnlarge.fillMode = .forwards
        testEnlarge.isRemovedOnCompletion = false
        
        let positionX = view.center.x - ((rectView.frame.width * 3)/2)
        let positionY = view.center.y - ((rectView.frame.height * 3)/2)
        
        let testTranslation = CAKeyframeAnimation(keyPath: "transform.translation")
        testTranslation.values = [CGPoint(x: positionX, y: positionY)]
        testTranslation.beginTime = CACurrentMediaTime() + 2
        testEnlarge.duration = 1
        testEnlarge.fillMode = .forwards
        testTranslation.isRemovedOnCompletion = false
        
        CATransaction.begin()
        rectView.layer.add(testEnlarge, forKey: nil)
        rectView.layer.add(testTranslation, forKey: nil)
        CATransaction.commit()
    }
}
Here is my **wrong demo**:

enter image description here


Solution

  • For your question, your code has two errors

    First one: When you use CAKeyframeAnimation for translation, in values you must define list Array which contains startPoint -> endPoint.

    // You must define start point is at the beginning (CGPointZero of view) to end is the destination point
    testTranslation.values = [CGPointZero, CGPoint(x: positionX, y: positionY)]
    

    Then after that your code will work OK but not good because of duplication and you can not control all animations like that.

    Second error here is: When you want to combine two or more animations you should use CAAnimationGroup to combine then add into layer not adding once by once.

    Code will be like this

    private func presentAnimation() {
        let testEnlarge = CABasicAnimation(keyPath: "transform.scale")
        testEnlarge.fromValue = 1
        testEnlarge.toValue = 3
        testEnlarge.fillMode = .forwards
        
        let positionX = view.center.x - ((rectView.frame.width * 3)/2)
        let positionY = view.center.y - ((rectView.frame.height * 3)/2)
        
        let testTranslation = CAKeyframeAnimation(keyPath: "transform.translation")
        // you must define start point is at the beginning (CGPointZero of view) to end is the destination point
        testTranslation.values = [CGPointZero, CGPoint(x: positionX, y: positionY)]
        
        let sumAnimation = CAAnimationGroup()
        sumAnimation.animations = [testTranslation, testEnlarge]
        sumAnimation.duration = 1
        sumAnimation.beginTime = CACurrentMediaTime() + 2
        
        CATransaction.begin()
        rectView.layer.add(sumAnimation, forKey: nil)
        CATransaction.commit()
    }
    

    You will have like the result you want Result

    More over: you can use UIView.animate too.