Animation in iOS: native solutions and third-party frameworks

Animation in iOS: native solutions and third-party frameworks

Today we will take a look at a couple of quite simple examples, learn about pros and cons of what Apple has to offer in terms of animation and get ourselves familiar with some third-party frameworks I personally find to be quite useful for handling animation. I will also show you how to use UIKit Dynamics and Motion Effects available starting from iOS 7.

UIKit Dynamics is a physical engine that allows you to change elements by adding behaviors, such as gravity, springs, forces, etc. And the engine takes care of everything else.

Motion Effects — allows you to create parallax effects that you, for example, see on the Home screen when slightly tilting your phone. It looks like Home screen is slightly moving in one direction while icons are moving into another. It creates an effect of depth you can easily implement in your own apps.

UIKit Dynamics

Let’s add UIView to the storyboard, make it gray and bind it using constraints. You’ve got to be attentive here since sometimes physical engine doesn’t handle this kind of binding well and you need to simply add an element somewhere in viewDidLoad() and specify its size using frame.

UIKit Dynamics storyboard

Next add several properties to ViewController class:

var animator: UIDynamicAnimator!
var gravity: UIGravityBehavior!
var collision: UICollisionBehavior!

Now we initiate this properties in viewDidLoad():

animator = UIDynamicAnimator(referenceView: view)
gravity = UIGravityBehavior(items: [box])
animator.addBehavior(gravity)

Start your animation in simulator to see how a grey square is falling somewhere behind the screen. Kinda fun but not much. Add UICollisionBehavior and assign true to translatesReferenceBoundsIntoBoundary. This property is responsible for system boundaries and if its value is true, views of your ViewController will be used as boundaries. Don’t forget to add the new behavior to animator object — it is responsible for all the calculations:

//add collision
collision = UICollisionBehavior(items: [box])
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)

Now the grey square is hitting the lower part of the screen — awesome!

Let’s go further and add a barrier. If barrier is added through the storyboard, the engine can’t calculate it precisely so we will add it manually:

//add barrier
let barrier = UIView(frame: CGRect(x: 0.0, y: 400.0, width: 200.0, height: 20.0))
barrier.backgroundColor = UIColor.blackColor()
view.addSubview(barrier)

The barrier is added and we need to add a collision:

let rightEdge = CGPoint(x: barrier.frame.origin.x + barrier.frame.size.width, y: barrier.frame.origin.y)
collision.addBoundaryWithIdentifier("barrier", fromPoint: barrier.frame.origin, toPoint: rightEdge)

UIKit Dynamics falling cube

Now everything is fine, the system works, the cube falls and collides. Let’s go further and add a little bit of elasticity to the cube:

let itemBehavior = UIDynamicItemBehavior(items: [box])
itemBehavior.elasticity = 0.6
animator.addBehavior(itemBehavior)

Apart from elasticity we have a couple of other properties up our sleeve to play with:

  • elasticity
  • friction
  • density
  • resistance
  • angularResistance

We can also monitor the moment when the cube is meeting obstacles. We’re signing controller as a delegate:

collision.collisionDelegate = self

And implement the following method:

extension ViewController: UICollisionBehaviorDelegate {
 
    func collisionBehavior(behavior: UICollisionBehavior, beganContactForItem item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, atPoint p: CGPoint) {
 
        //change box color, when it collide
        if let itemView = item as? UIView {
              itemView.backgroundColor = UIColor.redColor()
        }
    }
}

And after each collision we will be changing cube’s color to red:

Changing color in UIKit Dynamics

Now let’s talk about what’s happening behind the scenes. Every behavior has action property. We can add a block of code our animation will execute during each step (about 400 milliseconds). Let’s add:

        //add trails
        collision.action = {
            self.box.backgroundColor = UIColor.clearColor()
            let newBox = UIView(frame: self.box.frame)
            newBox.layer.borderWidth = 1.0
            newBox.layer.borderColor = UIColor.lightGrayColor().CGColor
            newBox.transform = self.box.transform
            newBox.center = self.box.center
            self.view.addSubview(newBox)
        }

On every step of the animation a new cube will be added that is using coordinates of our grey cube while our “main character” is hidden so that we can enjoy the effect we’ve created :)

How UIKit Dynamics works

Here’s the final result:

UIKit Dynamics final result

Motion Effects

For the next example you’ll need a real device. Simulator won’t do — I’ve checked it myself: you can tilt your head or your monitor — it doesn’t help, I swear XD

Here’s our storyboard:

Motion Effects storyboard

Pay attention to constraints for backImView as well:

Motion Effects constraints for storyboard

It is done so that when backImView (image with the sky) shifts, it won’t show a white color of the view controller. And here’s the code:

override func viewDidLoad() {
        super.viewDidLoad()
 
        addMotionEffectToView(backImView, magnitude: 50.0)
        addMotionEffectToView(frontImView, magnitude: -20.0)
    }
 
//MARK: Helpers
    func addMotionEffectToView(view: UIView, magnitude: CGFloat) {
 
        let xMotion = UIInterpolatingMotionEffect(keyPath: "center.x", type: .TiltAlongHorizontalAxis)
        xMotion.minimumRelativeValue = -magnitude
        xMotion.maximumRelativeValue = magnitude
 
        let yMotion = UIInterpolatingMotionEffect(keyPath: "center.y", type: .TiltAlongVerticalAxis)
        yMotion.minimumRelativeValue = -magnitude
        yMotion.maximumRelativeValue = magnitude
 
        let group = UIMotionEffectGroup()
        group.motionEffects = [xMotion, yMotion]
 
        view.addMotionEffect(group)
    }

We will add motion effect to the back (backImView) and front view (frontImView). We aren’t interested in midImView here, it stays static.

Create a supplementary method called addMotionEffectToView and initialize it with your UIImageView with additional properties — and that’s it! Enjoy your parallax effect :)

Pop

Let’s proceed to the third-party frameworks. The first one in line is Pop framework from Facebook. In addition to standard animation (POPBasicAnimation) this framework supports POPSpringAnimation and POPDecayAnimation which allow you to receive realistic animation with the correct physics.

We will use cocoapods as an example. Add the following to Podfile:

target 'Animation_frameworks' do
use_frameworks!
 
pod 'pop', '~> 1.0'
 
end

Add several elements and segue for transitioning to the screen with a Scotch terrier to your storyboard. Now take a second and look how cute this dog is :)

Implementing animation with Pop framework in iOS

After clicking on each element a specified animation is shown. Let’s start with animateLabel() method:

func animateLabel() {
        let anim = POPSpringAnimation(propertyNamed: kPOPLayerScaleX)
        anim.fromValue = 0
        anim.toValue = 1
        anim.dynamicsFriction = 7
 
        anim.completionBlock = { anim, finished in
            print("Label animation completed: \(anim)")
        }
 
        label.layer.pop_removeAllAnimations()
        label.layer.pop_addAnimation(anim, forKey: "scaleX_animation")
    }

Start with creating POPSpringAnimation object by initializing it with the name of the property you’re going to work with. After clicking kPOPLayerScaleX go to the file in which you’ll see all the methods that can be animated.

Notice that you can animate both CALayer property names and UIView property names — your choice will define whether object or its layer is animated. I’ve added completionBlock for illustrative purposes so that you can see when animation ends.

Before adding animation it is a good idea to delete the one that already exists, if it wasn’t used — you know, just in case:

label.layer.pop_removeAllAnimations

Add animation:

label.layer.pop_addAnimation(anim, forKey: “scaleX_animation")

Key is arbitrary. In the end our UILabel object starts stretching along the X axis.

The next method:

func animateSegCtrl() {
        //UIView animation
        let anim = POPSpringAnimation(propertyNamed: kPOPViewAlpha)
        anim.fromValue = 0.0
        anim.toValue = 1.0
        anim.dynamicsFriction = 3.0
 
        segCtrl.pop_removeAllAnimations()
        segCtrl.pop_addAnimation(anim, forKey: "alpha_animation")
    }

After UISegmentedControl is clicked we start blinking animation of the alpha property.

Let’s move on and take a look at a more complex example:

func animateOrangeBox() {
        orangeBox.pop_removeAllAnimations()
 
        let anim = POPSpringAnimation(propertyNamed: kPOPLayerBounds)
        anim.toValue = NSValue(CGRect: CGRect(x: orangeBox.bounds.origin.x, y: orangeBox.bounds.origin.y, width: 200.0, height: 20.0))
        anim.springBounciness = 8
        orangeBox.pop_addAnimation(anim, forKey: "stretchRound")
 
        let cornerRadius = POPBasicAnimation(propertyNamed: kPOPLayerCornerRadius)
        cornerRadius.duration = 0.3
        cornerRadius.toValue = 10.0 //half of height
        cornerRadius.setValue("animProp", forKey: "cornerRadKey")
        cornerRadius.delegate = self
        orangeBox.layer.pop_addAnimation(cornerRadius, forKey: "cornerRadius")
    }

We will use POPSpringAnimation once more but this time kPOPLayerBounds will be animated. This animation is responsible for changing sizes (frame) of UIView. We start with making UIView look like a status bar.

Next we animate cornerRadius (simply use POPBasicAnimation) and sign as delegated to pop_animationDidStop. Implementing the method itself:

extension ViewController: POPAnimationDelegate {
    func pop_animationDidStop(anim: POPAnimation!, finished: Bool) {
        if finished {
            if anim.valueForKey("cornerRadKey")!.isEqual("animProp") {
                print("corner radius did finished")
                self.addProgressBarAnimation()
            }
        }
    }
}

Next we call out one more animation in the body of the method:

 func addProgressBarAnimation() {
        bar.frame = CGRectInset(orangeBox.frame, 4.0, 2.0)
        bar.layer.cornerRadius = 5.0
 
        let alphaAnim = POPBasicAnimation(propertyNamed: kPOPViewAlpha)
        alphaAnim.duration = 0.5
        alphaAnim.toValue = 0.8
        bar.pop_addAnimation(alphaAnim, forKey: "anim")
 
        let anim = POPBasicAnimation(propertyNamed: kPOPLayerBounds)
        anim.fromValue = NSValue(CGRect: CGRect(x: bar.frame.origin.x, y: bar.frame.origin.y, width: 0.0, height: bar.frame.size.height))
        anim.toValue = NSValue(CGRect: bar.frame)
        anim.duration = 3.0
        anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        bar.pop_addAnimation(anim, forKey: "bar")
    }

In the end we get the following transformation of a simple orange cube in the status bar :)

Animation in iOS with Pop framework

And the last type of animation I’d like to talk about here is a custom segue animation.

Here’s how to create a similar segue in storyboard:

Creating seague in storyboard

CustomSegue is a class inherited from UIStoryboardSegue that is responsible for animation when transitioning between controllers.

To create a simple animation we will rewrite a single method:

override func perform() {
        let sourceViewController = self.sourceViewController
        let destinationController = self.destinationViewController
 
        let layer = destinationController.view.layer
        layer.pop_removeAllAnimations()
 
        let sizeAnim = POPSpringAnimation(propertyNamed: kPOPLayerSize)
        sizeAnim.fromValue = NSValue(CGSize: CGSize(width: 10.0, height: 20.0))
 
        let rotAnim = POPSpringAnimation(propertyNamed: kPOPLayerRotationY)
        rotAnim.springSpeed = 3
        rotAnim.dynamicsFriction = 3
        rotAnim.fromValue = M_PI_4
        rotAnim.toValue = 0
 
        layer.pop_addAnimation(rotAnim, forKey: "rotation")
        layer.pop_addAnimation(sizeAnim, forKey: "size")
 
        rotAnim.completionBlock = { _, _ in
            print("Rotation animation completed!")
        }
        sizeAnim.completionBlock = { _, _ in
            print("Size animation completed!")
        }
 
        //animated should be 'false'!
        sourceViewController.navigationController?.pushViewController(destinationController, animated: false)
    }

As a result, we made animation more lively. Of course, in a real app we would also have to comply with UIViewControllerTransitioningDelegate protocol and implement two of its methods: animationControllerForPresentedController and animationControllerForDismissedController. Here I simply wanted to show that it is relatively easy to get the desired result with Pop framework.

Cheetah

Personally, I like Cheetah framework. It is amazingly compact in terms of code and allows you to receive quite good results.

Open Cheetah_project. In storyboard you have one button we’re going to click at all times :) Its code is available below:

@IBAction func buttonAction(sender: AnyObject) {
        box.cheetah
            .scale(1.5)
            .duration(1.0)
            .spring()
            .cornerRadius(20.0)
            .wait()
 
	//reset
            .scale(1.0)
            .cornerRadius(0.0)
            .duration(0.5)
 
            .run()
    }

After writing a couple of code lines we get animation that would take us much more time if we used standard methods. And, of course, the more code we have, the harder it is to change.

Animation in iOS  using Cheetah framework

I would also recommend you to check out Spring framework. Developers have prepared a very nice app you can use to explore capabilities of their product and even take a look at the code of the effect you’re interested in. DCAnimationKit is a very simple framework with a lot of ready-made decisions that is also worth checking out.

Don’t forget that these are ready-made solutions written with low-level C functions, such as CGAffineTransformMakeRotation(), and if you’re serious about animation, you’ve got to deal with them.

Or, at least, you’ve got to be familiar with higher level methods, such as:

  • UIView.animateWithDuration
  • UIView.animateKeyframesWithDuration
  • UIView.addKeyframeWithRelativeStartTime

And you should understand CABasicAnimation(), CAPropertyAnimation(), etc. as well.

But if you just need to quickly implement a certain function, then one of the more “lazy solutions” described above is what you actually need.

Need MVP development, iOS and Android apps or prototyping? Check out our portfolio and make an order today!