Performing iOS Animations On Views With UIKit And UIView

I have been an iOS developer for over a decade now and have rarely seen articles that consolidate all possible ways to perform animations in iOS. This article aims to be a primer on iOS animations with the intent of exhaustively covering the different ways of doing the same.
Given the extensiveness of the topic, we would cover each part succinctly at a fairly high level. The goal is to educate the reader with a set of choices to add animations to his/ her iOS app.

Performing iOS Animations On Views With UIKit And UIView Check this!

Did you notice the difference? Human eyes can definitely feel the jitter at lower fps. Hence, it is always a good practice to make sure that any animation you create, adheres to the ground rule of running at 60FPS or higher. This makes it feel more realistic and alive.

Having looked at FPS, let’s now delve into the different core iOS frameworks that provide us a way to perform animations.

Core Frameworks

In this section, we will touch upon the frameworks in the iOS SDK which can be used for creating view animations. We will do a quick walk through each of them, explaining their feature set with a relevant example.

UIKit/ UIView Animations

UIView is the base class for any view that displays content in iOS apps.

UIKit, the framework that gives us UIView, already provides us some basic animation functions which make it convenient for developers to achieve more by doing less.

The API, UIView.animate, is the easiest way to animate views since any view’s properties can be easily animated by providing the property values in the block-based syntax.

In UIKit animations, it is recommended to modify only the animatable properties of UIVIew else there will be repercussions where the animations might cause the view to end up in an unexpected state.

animation(withDuration: animations: completion)

This method takes in the animation duration, a set of view’s animatable property changes that need to be animated. The completion block gives a callback when the view is done with performing the animation.

Almost any kind of animation like moving, scaling, rotating, fading, etc. on a view can be achieved with this single API.

Now, consider that you want to animate a button size change or you want a particular view to zoom into the screen. This is how we can do it using the UIView.animate API:

let newButtonWidth: CGFloat = 60 UIView.animate(withDuration: 2.0) { //1 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) //2 self.button.center = self.view.center //3
}

Here’s what we are doing here:

  1. We call the UIView.animate method with a duration value passed to it that represents how long the animation, described inside the block, should run.
  2. We set the new frame of the button that should represent the final state of the animation.
  3. We set the button center with its superview’s center so that it remains at the center of the screen.

The above block of animation code should trigger the animation of the button’s frame changing from current frame:

Width = 0, Height = 0

To the final frame:

Width = Height = newButtonWidth

And here’s what the animation would look like:

animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion

This method is like an extension of the animate method where you can do everything that you can perform in the prior API with some physics behaviors added to the view animations.

For example, if you want to achieve spring damping effects in the animation that we have done above, then this is how the code would look like:

let newButtonWidth: CGFloat = 60
UIView.animate(withDuration: 1.0, //1 delay: 0.0, //2 usingSpringWithDamping: 0.3, //3 initialSpringVelocity: 1, //4 options: UIView.AnimationOptions.curveEaseInOut, //5 animations: ({ //6 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center
}), completion: nil)

Here’s the set of parameters we use:

  1. duration
    Represents the duration of the animation determining how long the block of code should run.
  2. delay
    Represents the initial delay that we want to have before the start of the animation.
  3. SpringWithDamping
    Represents the value of the springy effect that we want the view to behave. The value must be between 0 to 1. The lower the value, the higher the spring oscillation.
  4. velocity
    Represents the speed at which the animation should start.
  5. options
    Type of animation curve that you want to apply to your view animation.
  6. Finally, the block of code where we set the frame of the button that needs to be animated. It is the same as the previous animation.

And here’s what the animation would look like with the above animation configuration:

UIViewPropertyAnimator

For a bit more control over animations, UIViewPropertyAnimator comes handy where it provides us a way to pause and resume animations. You can have custom timing and have your animation to be interactive and interruptible. This is very much helpful when performing animations that are also interactable with user actions.

The classic ‘Slide to Unlock’ gesture and the player view dismiss/ expand animation (in the Music app) are examples of interactive and interruptible animations. You can start moving a view with your finger, then release it and the view will go back to its original position. Alternatively, you can catch the view during the animation and continue dragging it with your finger.

Following is a simple example of how we could achieve the animation using UIViewPropertyAnimator:

let newButtonWidth: CGFloat = 60
let animator = UIViewPropertyAnimator(duration:0.3, curve: .linear) { //1 self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center
}
animator.startAnimation() //2

Here’s what we are doing:

  1. We call the UIViewProperty API by passing the duration and the animation curve.
  2. Unlike both the above UIView.animate API’s, the animation won’t start unless you specify it by yourself i.e. you’re in full control of the complete animation process/ flow.

Now, let’s say that you want even more control over the animations. For example, you want to design and control each and every frame in the animation. There’s another API for that, animateKeyframes. But before we delve into it, let’s quickly look at what a frame is, in an animation.

What Is A frame?

A collection of the view’s frame changes/ transitions, from the start state to the final state, is defined as animation and each position of the view during the animation is called as a frame.

animateKeyframes

This API provides a way to design the animation in such a way that you can define multiple animations with different timings and transitions. Post this, the API simply integrates all the animations into one seamless experience.

Let’s say that we want to move our button on the screen in a random fashion. Let’s see how we can use the keyframe animation API to do so.

UIView.animateKeyframes(withDuration: 5, //1 delay: 0, //2 options: .calculationModeLinear, //3 animations: { //4 UIView.addKeyframe( //5 withRelativeStartTime: 0.25, //6 relativeDuration: 0.25) { //7 self.button.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.maxY) //8 } UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25) { self.button.center = CGPoint(x: self.view.bounds.width, y: start.y) } UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) { self.button.center = start }
})

Here’s the breakdown:

  1. duration
    Call the API by passing in the duration of the animation.
  2. delay
    Initial delay duration of the animation.
  3. options
    The type of animation curve that you want to apply to your view animation.
  4. animations
    Block that takes all keyframe animations designed by the developer/ user.
  5. addKeyFrame
    Call the API to design each and every animation. In our case, we have defined each move of the button. We can have as many such animations as we need, added to the block.
  6. relativeStartTime
    Defines the start time of the animation in the collection of the animation block.
  7. relativeDuration
    Defines the overall duration of this specific animation.
  8. center
    In our case, we simply change the center property of the button to move the button around the screen.

And this is how the final animations looks like:

CoreAnimation

Any UIKit based animation is internally translated into core animations. Thus, the Core Animation framework acts as a backing layer or backbone for any UIKit animation. Hence, all UIKit animation APIs are nothing but encapsulated layers of the core animation APIs in an easily consumable or convenient fashion.

UIKit animation APIs don’t provide much control over animations that have been performed over a view since they are used mostly for animatable properties of the view. Hence in such cases, where you intend to have control over every frame of the animation, it is better to use the underlying core animation APIs directly. Alternatively, both the UIView animations and core animations can be used in conjunction as well.

UIView + Core Animation

Let’s see how we can recreate the same button change animation along with specifying the timing curve using the UIView and Core Animation APIs.

We can use CATransaction’s timing functions, which lets you specify and control the animation curve.

Let’s look at an example of a button size change animation with its corner radius utilizing the CATransaction’s timing function and a combination of UIView animations:

let oldValue = button.frame.width/2
let newButtonWidth: CGFloat = 60 /* Do Animations */
CATransaction.begin() //1
CATransaction.setAnimationDuration(2.0) //2
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) //3 // View animations //4
UIView.animate(withDuration: 1.0) { self.button.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth) self.button.center = self.view.center
} // Layer animations
let cornerAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.cornerRadius)) //5
cornerAnimation.fromValue = oldValue //6
cornerAnimation.toValue = newButtonWidth/2 //7 button.layer.cornerRadius = newButtonWidth/2 //8
button.layer.add(cornerAnimation, forKey: #keyPath(CALayer.cornerRadius)) //9 CATransaction.commit() //10

Here’s the breakdown:

  1. begin
    Represents the start of the animation code block.
  2. duration
    Overall animation duration.
  3. curve
    Represents the timing curve that needs to be applied to the animation.
  4. UIView.animate
    Our first animation to change the frame of the button.
  5. CABasicAnimation
    We create the CABasicAnimation object by referring the cornerRadius of the button as the keypath since that’s what we want to animate. Similarly, if you want to have granular level control over the keyframe animations, then you can use the CAKeyframeAnimation class.
  6. fromValue
    Represents the starting value of the animation, i.e. the initial cornerRadius value of the button from where the animation must start off.
  7. toValue
    Represents the final value of the animation, i.e. the final cornerRadius value of the button where the animation must end.
  8. cornerRadius
    We must set the cornerRadius property of the button with the final value of the animation else the button’s cornerRadius value will get auto-reverted to its initial value after the animation completes.
  9. addAnimation
    We attach the animation object that contains the configuration of the entire animation process to the layer by representing the Keypath for which the animation needs to be performed.
  10. commit
    Represents the end of the animation code block and starts off the animation.

This is how the final animation would look like:

This blog is a great read to help create more advanced animations as it neatly walks you through most of the Core Animation framework APIs with instructions guiding you through every step of the way.

UIKitDynamics

UIKit Dynamics is the physics engine for UIKit which enables you to add any physics behaviors like collision, gravity, push, snap, etc, to the UIKit controls.

UIKitDynamicAnimator

This is the admin class of the UIKit Dynamics framework that regulates all animations triggered by any given UI control.

UIKitDynamicBehavior

It enables you to add any physics behavior to an animator which then enables it to perform on the view attached to it.

Different kinds of behaviors for UIKitDynamics include:

  • UIAttachmentBehavior
  • UICollisionBehavior
  • UIFieldBehavior
  • UIGravityBehavior
  • UIPushBehavior
  • UISnapBehavior

The architecture of UIKitDynamics looks something like this. Note that Items 1 to 5 can be replaced with a single view.

Let us apply some physics behavior to our button. We will see how to apply gravity to the button so that it gives us a feeling of dealing with a real object.

var dynamicAnimator : UIDynamicAnimator!
var gravityBehavior : UIGravityBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2
dynamicAnimator.addBehavior(gravityBehavior) //3

Here’s the breakdown:

  1. UIKitDynamicAnimator
    We have created a UIKitDynamicAnimator object which acts as an orchestrator for performing animations. We have also passed the superview of our button as the reference view.
  2. UIGravityBehavior
    We have created a UIGravityBehavior object and pass our button into the array elements on which this behavior is injected.
  3. addBehavior
    We have added the gravity object to the animator.

    This should create an animation as shown below:

    Notice how the button falls off from the center (its original position) of the screen to the bottom and beyond.

    We should tell the animator to consider the bottom of the screen to be the ground. This is where UICollisionBehavior comes into picture.

    var dynamicAnimator : UIDynamicAnimator!
    var gravityBehavior : UIGravityBehavior!
    var collisionBehavior : UICollisionBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2
    dynamicAnimator.addBehavior(gravityBehavior) //3 collisionBehavior = UICollisionBehavior(items: [button]) //4
    collisionBehavior.translatesReferenceBoundsIntoBoundary = true //5
    dynamicAnimator.addBehavior(collisionBehavior) //6
  4. UICollisionBehavior
    We have created a UICollisionBehavior object and passed along the button so that the behavior is added to the element.
  5. translatesReferenceBoundsIntoBoundary
    Enabling this property tells the animator to take the reference views boundary as the end, which is the bottom of the screen in our case.
  6. addBehavior
    We have added collision behavior to the animator here.

    Now, our button should hit the ground and stand still as shown below:

    That’s pretty neat, isn’t it?

    Now, let us try adding a bouncing effect so that our object feels more real. To do that, we will use the UIDynamicItemBehavior class.

    var dynamicAnimator : UIDynamicAnimator!
    var gravityBehavior : UIGravityBehavior!
    var collisionBehavior : UICollisionBehavior!
    var bouncingBehavior : UIDynamicItemBehavior! dynamicAnimator = UIDynamicAnimator(referenceView: self.view) //1 gravityBehavior = UIGravityBehavior(items: [button]) //2
    dynamicAnimator.addBehavior(gravityBehavior) //3 collisionBehavior = UICollisionBehavior(items: [button]) //4
    collisionBehavior.translatesReferenceBoundsIntoBoundary = true //5
    dynamicAnimator.addBehavior(collisionBehavior) //6 //Adding the bounce effect
    bouncingBehavior = UIDynamicItemBehavior(items: [button]) //7
    bouncingBehavior.elasticity = 0.75 //8
    dynamicAnimator.addBehavior(bouncingBehavior) //9
  7. UIDynamicItemBehavior
    We have created a UIDynamicItemBehavior object and pass along the button so that the behavior is added to the element.
  8. elasticity
    Value must be between 0-1, it represents the elasticity i.e. the number of times the object must bounce on and off the ground when it is hit. This is where the magic happens — by tweaking this property, you can differentiate between different kinds of objects like balls, bottles, hard-objects and so on.
  9. addBehavior
    We have added collision behavior to the animator here.

Now, our button should bounce when it hits the ground as shown below:

This repo is quite helpful and shows all UIKitDynamics behaviors in action. It also provides source code to play around with each behavior. That, in my opinion, should serve as an extensive list of ways to perform iOS animations on views!

In the next section, we will take a brief look into the tools that will aid us in measuring the performance of animations. I would also recommend you to look at ways to optimize your Xcode build since it will save a huge amount of your development time.

Performance Tuning

In this section, we will look at ways to measure and tune the performance of iOS animations. As an iOS developer, you might have already used Xcode Instruments like Memory Leaks and Allocations for measuring the performance of the overall app. Similarly, there are instruments that can be used to measure the performance of animations.

Core Animation Instrument

Try the Core Animation instrument and you should be able to see the FPS that your app screen delivers. This is a great way to measure the performance/ speed of any animation rendered in your iOS app.

Drawing

FPS is vastly lowered in the app that displays heavy content like images with effects like shadows. In such cases, instead of assigning the Image directly to the UIImageView’s image property, try to draw the image separately in a context using Core Graphics APIs. This overly reduces the image display time by performing the image decompression logic asynchronously when done in a separate thread instead of the main thread.

Rasterization

Rasterization is a process used to cache complex layer information so that these views aren’t redrawn whenever they’re rendered. Redrawing of views is the major cause of the reduction in FPS and hence, it is best to apply rasterization on views that are going to be reused several times.

Wrapping Up

To conclude, I have also summed up a list of useful resources for iOS animations. You may find this very handy when working on iOS animations. Additionally, you may also find this set of design tools helpful as a (design) step before delving into animations.

I hope I have been able to cover as many topics as possible surrounding iOS animations. If there is anything I may have missed out in this article, please let me know in the comments section below and I would be glad to make the addition!

Smashing Editorial (dm, yk, il)

Posted by Web Monkey