Fudgey
Fudgey

Reputation: 3833

Using CAMediaTimingFunction to animate a UIView NSLayoutConstraint

I'm currently animating one of my constraints in an animation block, however I wish to customise the animation type - further to that given by the preset UIVIewAnimationOptions:

    UIView.animateWithDuration(2.0, delay: 0.3, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in

        self.heightTransition.constant = self.view.bounds.height - 52
        self.view.layoutIfNeeded()

        }, completion: { (complete) -> Void in
    })

I've looked into the potential of using a CAMediaTimingFunction, as shown here (http://cubic-bezier.com/#.44,.94,.79,-0.01), where you can pass in values to alter the animation style.

My question then is, how can I apply the use of CAMediaTimingFunction upon animating UIView constraints?

Upvotes: 4

Views: 600

Answers (1)

SomeUser
SomeUser

Reputation: 35

I don't know if it's possible to do it directly, because NSLayoutConstraint is part of the UIKit, while CALayer is part of the Core Animation. But for me works this approach, where i do my CABasicAnimation with custom cubic bezier animation curve, then on completion UIView returns to it's starting position defined by it's constraints before animation started(or i can set other constraints to my UIView in animation completion block.), you can paste this in empty new project if you want to play around:

        import UIKit
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            super.view.backgroundColor = .white
//Button to trigger animation.
            let button: UIButton = {
                let button = UIButton()
                super.view.addSubview(button)
                button.setTitleColor(UIColor.systemCyan, for: .normal)
                button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
                button.setTitle("Animate!", for: .normal)
                button.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    button.topAnchor.constraint(equalTo: super.view.topAnchor, constant: super.view.frame.height * 0.886),
                    button.centerXAnchor.constraint(equalTo: super.view.centerXAnchor),
                    button.heightAnchor.constraint(equalToConstant: super.view.frame.height * 0.024),
                ])
                return button
            }()
        }
        @objc func buttonPressed() {
            //Remove view created previous button press.
            for view in super.view.subviews {
                if view.backgroundColor == .systemMint {
                    view.removeFromSuperview()
                }
            }
            //heightConstraint is distance we want to walk from start to finish of animation.
            var heightConstraint: NSLayoutConstraint!
            var topConstraint: NSLayoutConstraint!
            let view: UIView = {
                let view = UIView()
                super.view.addSubview(view)
                view.translatesAutoresizingMaskIntoConstraints = false
                view.layer.masksToBounds = true
                view.backgroundColor = .systemMint
                //After animation completion view will return to this constraints.
                heightConstraint = view.heightAnchor.constraint(equalToConstant: super.view.frame.height * 0.605)
                topConstraint = view.topAnchor.constraint(equalTo: super.view.topAnchor, constant: -600)
                NSLayoutConstraint.activate([
                    topConstraint,
                    view.widthAnchor.constraint(equalToConstant: super.view.frame.width * 0.87),
                    heightConstraint,
                    view.centerXAnchor.constraint(equalTo: super.view.centerXAnchor)
                ])
                view.layer.cornerRadius = super.view.frame.height * 0.0495
                return view
            }()
            //Create custom animation curve based on cubic bezier controlPoints
            let timingFunction = CAMediaTimingFunction(controlPoints: 0,1.07,0.15,0.97 /*0,1,0,1*/)
            //We can't animate our topConstraint directly, so we'll use "position.y"
            let animation = CABasicAnimation(keyPath: "position.y")
            animation.duration = 3
            //position.y counted from midY, so we have to subtract half of view's height
            //so view's bottom == superview's top is our starting point
            animation.fromValue = -heightConstraint.constant/2
            //and so with finish point
            animation.toValue = heightConstraint.constant/2
            animation.timingFunction = timingFunction
            animation.fillMode = .forwards
            animation.isRemovedOnCompletion = true
            CATransaction.setCompletionBlock({
                /*Here set necessary constraints manually,
                 on completion the will be applied.
                 (Early we set this constraint to -600, so when animation
                 finished - our UIView returns to it's position
                 before animation started, preserving constraints.
                 We can set it to 0 here, or we could do it earlier
                 instead of -600, depends of what we need.)*/
                topConstraint.constant = 0
                        })
            view.layer.add(animation, forKey: nil)
        }
    }

As we can see in gif, constraints set to UIView preserved after animation completion.

Upvotes: 1

Related Questions