iOS 10 Animation using UIViewPropertyAnimator

PreviousTable of ContentsNext
An iOS 10 Graphics Tutorial using Core Graphics and Core ImageiOS 10 UIKit Dynamics – An Overview


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


The majority of the visual effects used throughout the iOS user interface are performed using UIKit animation. UIKit provides a simple mechanism for implementing basic animation within an iOS application. If you need a user interface element to gently fade in or out of view, slide smoothly across the screen or gracefully resize or rotate before the user’s eyes, these effects can be achieved using UIKit animation in just a few lines of code.

In this chapter we will provide an overview of the basics of UIKit animation and work through a simple example. While much can be achieved with UIKit animation, however, it should be noted that if you plan to develop a graphics intensive 3D style application then it is more likely that OpenGL ES or SceneKit will need to be used, a subject area to which numerous books are dedicated.

The Basics of UIKit Animation

The cornerstone of animation in UIKit is the UIViewPropertyAnimator class. This class allows the changes made to the properties of a view object to be animated using a range of options.

As an example, consider a UIView object that contains a UIButton connected to an outlet named theButton. The application requires that the button gradually fade from view over a period of 3 seconds. This can be achieved by making the button transparent through the use of the alpha property:

theButton.alpha = 0

Simply setting the alpha property to 0, however, causes the button to immediately become transparent. In order to make it fade out of sight gradually we need to create a UIViewPropertyAnimator instance configured with the duration of the animation. This class also needs to know the animation curve of the animation. This curve is used to control the speed of the animation as it is running. An animation might start out slow, speed up and then slow down again before completion. The timing curve of an animation is controlled by the UICubicTimingParameters and UISpringTimingParameters classes. The following code configures a UIViewPropertyAnimator instance using the standard “ease in” animation curve dispersed over a 2 second duration:

let timing = UICubicTimingParameters(animationCurve: .easeIn)
let animator = UIViewPropertyAnimator(duration: 2.0, 
				timingParameters:timing)

Once the UIViewPropertyAnimator class has been initialized, the animation sequence to be performed needs to be added, followed by a call the object’s startAnimation method:

animator.addAnimations {
    self.theButton.alpha = 0
}
animator.startAnimation()

A range of other options are available for use when working with a UIViewPropertyAnimator instance. Animation may be paused or stopped at any time via calls to the pauseAnimation and stopAnimation methods respectively. To configure the animator to call a completion handler when the animation finishes, simply assign the handler to the object’s completion property. The animation may also be reversed by assigning a true value to the isReversed property. The start of the animation may be delayed by passing through a delay duration when initializing the UIViewPropertyAnimator class as follows:

animator.startAnimation(afterDelay: 4.0) 

Understanding Animation Curves

As previously mentioned, in addition to specifying the duration of an animation sequence, the linearity of the animation timeline may also be defined by specifying an animation curve. This setting controls whether the animation is performed at a constant speed, whether it starts out slow and speeds up and also provides options for adding spring-like behavior to an animation.

The UICubicTimingParameters class is used to configure time based animation curves. As demonstrated in the previous section, one option when using this class is to use one of the following four standard animation curves provided by UIKit:

.curveLinear – The animation is performed at constant speed for the specified duration and is the option declared in the above code example.

.curveEaseOut – The animation starts out fast and slows as the end of the sequence approaches.

.curveEaseIn – The animation sequence starts out slow and speeds up as the end approaches.

.curveEaseInOut – The animation starts slow, speeds up and then slows down again.

If the standard options do not meet your animation needs, a custom cubic curve may be created and used as the animation curve simply by specifying control points:

let timing = UICubicTimingParameters(
		controlPoint1: CGPoint(x:0.0, y:1.0),
               controlPoint2: CGPoint(x:1.0,y:0.0))

Alternatively, property changes to a view may be animated using a spring effect using the UISpringTimingParameters class. Instances of this class can be configured using mass, spring “stiffness”, damping and velocity values as follows:

let timing = UISpringTimingParameters(mass: 0.5, stiffness: 0.5, 
	damping: 0.3, initialVelocity: CGVector(dx:1.0, dy: 0.0))

Alternatively, the spring effect may be configured using just the damping ratio and velocity:

let timing = UISpringTimingParameters(dampingRatio: 0.4, 
		initialVelocity: CGVector(dx:1.0, dy: 0.0))

Performing Affine Transformations

Transformations allow changes to be made to the coordinate system of a screen area. This essentially allows the programmer to rotate, resize and translate a UIView object. A call is made to one of a number of transformation functions and the result assigned to the transform property of the UIView object.

For example, to change the scale of a UIView object named myView by a factor of 2 in both height and width:

myView.transform = CGAffineTransform(scaleX: 2, y: 2)

Similarly, the UIView object may be rotated using the CGAffineTransform(rotationAngle:) function which takes as an argument the angle (in radians) by which the view is to be rotated. The following code, for example, rotates a view by 90 degrees:

let angle = CGFloat(90 * M_PI / 180)
myView.transform = CGAffineTransform(rotationAngle: angle)

The key point to keep in mind with transformations is that they become animated effects when performed within an animation sequence. The transformations evolve over the duration of the animation and follow the specified animation curve in terms of timing.

Combining Transformations

Two transformations may be combined to create a single transformation effect via a call to the concatenating method of the first transformation instance, passing through the second transformation object as an argument. This function takes as arguments the two transformation objects that are to be combined. The result may then be assigned to the transform property of the UIView object to be transformed. The following code fragment, for example, creates a transformation combining both scale and rotatation:

let scaleTrans = CGAffineTransform(scaleX: 2, 2)

let angle = CGFloat(90 * M_PI / 180)
let rotateTrans = CGAffineTransform(rotationAngle: angle)

scaleTrans.concatenating(rotateTrans)

Affine transformations offer an extremely powerful and flexible mechanism for creating animations and it is just not possible to do justice to these capabilities in a single chapter. In order to learn more about affine transformations, a good starting place is the Transforms chapter of Apple’s Quartz 2D Programming Guide.

Creating the Animation Example Application

The remainder of this chapter is dedicated to the creation of an iOS application intended to demonstrate the use of UIKit animation. The end result is a simple application on which a blue square appears. When the user touches a location on the screen the box moves to that location using a spring-based animation curve. Through the use of affine transformations, the box will rotate 180 degrees as it moves to the new location while also changing in size and color. Finally, a completion handler will change the color a second time once the animation has finished.

Begin by launching Xcode and creating a new Single View Application project named Animate using Swift as the programming language and with the Universal device option selected.

Implementing the Variables

For the purposes of this application we will need a UIView to represent the blue square and variables to contain the rotation angle and scale factor by which the square will be transformed. These need to be declared in the ViewController.swift file as follows:

import UIKit

class ViewController: UIViewController {

    var scaleFactor: CGFloat = 2
    var angle: Double = 180
    var boxView: UIView?
.
.
.

Drawing in the UIView

Having declared the UIView reference, we now need to initialize an instance object and draw a blue square located at a specific location on the screen. We also need to add boxView as a subview of the application’s main view object. These tasks only need to be performed once when the application first starts up so a good option is to use the viewDidLoad method in the ViewController.swift file:

override func viewDidLoad() {
    super.viewDidLoad()

    let frameRect = CGRect(x: 20, y: 20, width: 45, height: 45)

    boxView = UIView(frame: frameRect)
    boxView?.backgroundColor = UIColor.blue
    self.view.addSubview(boxView!)
}

Detecting Screen Touches and Performing the Animation

When the user touches the screen the blue box needs to move from its current location to the location of the touch. During this motion, the box will rotate 180 degrees and change in size. The detection of screen touches was covered in detail in An Overview of iOS 10 Multitouch, Taps and Gestures. For the purposes of this example we want to initiate the animation at the point that the user’s finger is lifted from the screen so we need to implement the touchesEnded method in the ViewController.swift file:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

    if let touch = touches.first {
        let location = touch.location(in: self.view)
        let timing = UICubicTimingParameters(
				animationCurve: .easeInOut)
        let animator = UIViewPropertyAnimator(duration: 2.0, 
				timingParameters:timing)

        animator.addAnimations {
            let scaleTrans =
                CGAffineTransform(scaleX: self.scaleFactor,
                                  y: self.scaleFactor)
            let rotateTrans = CGAffineTransform(
                rotationAngle: CGFloat(self.angle * M_PI / 180))

            self.boxView!.transform =
                scaleTrans.concatenating(rotateTrans)

            self.angle = (self.angle == 180 ? 360 : 180)
            self.scaleFactor = (self.scaleFactor == 2 ? 1 : 2)
            self.boxView?.backgroundColor = UIColor.purple
            self.boxView?.center = location
        }

        animator.addCompletion {_ in
            self.boxView?.backgroundColor = UIColor.green
        }
        animator.startAnimation()
    }
}

Before compiling and running the application we need to take some time to describe the actions performed in the above method. First, the method gets the UITouch object from the touches argument and the location(in:) method of this object is called to identify the location on the screen where the touch took place:

if let touch = touches.first {
    let location = touch.location(in: self.view))

An instance of the UICubicTimingParameters class is then created and configured with the standard ease in, ease out animation curve:

let timing = UICubicTimingParameters(animationCurve: .easeInOut)

The animation object is then created and initialized with the timing object and a duration value of 2 seconds:

let animator = UIViewPropertyAnimator(duration: 2.0, 
				timingParameters:timing)

The animation closure is then added to the animation object. This begins the creation of two transformations for the view, one to scale the size of the view and one to rotate it 180 degrees. These transformations are then combined into a single transformation and applied to the UIView object:

let scaleTrans =
            CGAffineTransform(scaleX: self.scaleFactor,
                                   y: self.scaleFactor)
let rotateTrans = CGAffineTransform(
                     rotationAngle: CGFloat(self.angle * M_PI / 180))

self.boxView?.transform = scaleTrans.concatenating(rotateTrans)

Ternary operators are then used to switch the scale and rotation angle variables ready for the next touch. In other words, after rotating 180 degrees on the first touch the view will need to be rotated to 360 degrees on the next animation. Similarly, once the box has been scaled by a factor of 2 it needs to scale back to its original size on the next animation:

self.angle = (self.angle == 180 ? 360 : 180)
self.scaleFactor = (self.scaleFactor == 2 ? 1 : 2)

Finally, the location of the view is moved to the point on the screen where the touch occurred and the color of the box changed to purple:

self.boxView?.backgroundColor = UIColor.purple
self.boxView?.center = location

Next, a completion handler is assigned to the animation and implemented such that it changes the color of the box view to green:

animator.addCompletion {_ in
    self.boxView?.backgroundColor = UIColor.green
}

After the animations have been added to the animation object, the animation sequence is started:

animator.startAnimation()

Once the touchesEnded method has been implemented it is time to try out the application.

Building and Running the Animation Application

Once all the code changes have been made and saved, click on the run button in the Xcode toolbar. Once the application has compiled it will load into the iOS Simulator or connected iOS device.

When the application loads the blue square should appear near the top left-hand corner of the screen. Click (or touch if running on a device) the screen and watch the box glide and rotate to the new location, the size and color of the box changing as it moves:


Ios 8 core animation running.png

Figure 64-1

Implementing Spring Timing

The final task in this tutorial is to try out the UISpringTimingParameters class to implement a spring effect at the end of the animation. Edit the ViewController.swift file and change the timing constant so that it reads as follows:

.
.
let timing = UICubicTimingParameters(animationCurve: .easeInOut)

let timing = UISpringTimingParameters(mass: 0.5, stiffness: 0.5, damping: 0.3, initialVelocity: CGVector(dx:1.0, dy: 0.0))
.
.

Run the app once more, tap the screen and note the spring effect on the box when it reaches the end location in the animation sequence.

Summary

UIKit animation provides an easy to implement interface to animation within iOS applications. From the simplest of tasks such as gracefully fading out a user interface element to basic animation and transformations, UIKit animation provides a variety of techniques for enhancing user interfaces. This chapter covered the basics of UIKit animation, including the UIViewPropertyAnimator, UISpringTimingParameters and UICubicTimingParameters classes before working step-by-step through an example to demonstrate the implementation of motion, rotation and scaling animation.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
An iOS 10 Graphics Tutorial using Core Graphics and Core ImageiOS 8 UIKit Dynamics – An Overview