Most 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 app. For example, if you need a user interface element to fade in or out of view gently, 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.
This chapter will introduce the basics of UIKit animation and work through a simple example. While much can be achieved with UIKit animation, if you plan to develop a graphics-intensive 3D style app, it is more likely that Metal 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.
For example, consider a UIView object containing a UIButton connected to an outlet named theButton. The app requires that the button gradually fades from view over 3 seconds. This can be achieved by making the button transparent through the use of the alpha property:
theButton.alpha = 0
Code language: Swift (swift)
However, setting the alpha property to 0 causes the button to become transparent immediately. 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. For example, an animation might start slow, speed up and then slow down again before completion. The timing curve of an animation is controlled by the UICubicTimingParameters and UISpringTimingParameters classes. For example, 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)
Code language: Swift (swift)
Once the UIViewPropertyAnimator class has been initialized, the animation sequence to be performed needs to be added, followed by a call to the object’s startAnimation method:
animator.addAnimations {
self.theButton.alpha = 0
}
animator.startAnimation()
Code language: Swift (swift)
A range of other options is available when working with a UIViewPropertyAnimator instance. Animation may be paused or stopped anytime via calls to the pauseAnimation and stopAnimation methods. To configure the animator to call a completion handler when the animation finishes, assign the handler to the object’s completion property. The animation may 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)
Code language: Swift (swift)
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 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 a constant speed for the specified duration and is the option declared in the above code example.
- .curveEaseOut – The animation starts fast and slows as the end of the sequence approaches.
- .curveEaseIn – The animation sequence starts slow and speeds up as the end approaches.
- .curveEaseInOut – The animation starts slow, speeds up, and 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))
Code language: Swift (swift)
Alternatively, property changes to a view may be animated using a spring effect via 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))
Code language: Swift (swift)
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))
Code language: Swift (swift)
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 several transformation functions, and the result is 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)
Code language: Swift (swift)
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 * .pi / 180)
myView.transform = CGAffineTransform(rotationAngle: angle)
Code language: Swift (swift)
The key point to remember 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 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 rotation:
let scaleTrans = CGAffineTransform(scaleX: 2, 2)
let angle = CGFloat(90 * .pi / 180)
let rotateTrans = CGAffineTransform(rotationAngle: angle)
scaleTrans.concatenating(rotateTrans)
Code language: Swift (swift)
Affine transformations offer an extremely powerful and flexible mechanism for creating animations, and it is impossible to do justice to these capabilities in a single chapter. However, a good starting place to learn about affine transformations is the Transforms chapter of Apple’s Quartz 2D Programming Guide.
Creating the Animation Example App
The remainder of this chapter is dedicated to creating an iOS app that demonstrates the use of UIKit animation. The result is a simple app 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.
Launch Xcode and create a new project using the iOS App template with the Swift and Storyboard options selected, entering Animate as the product name.
Implementing the Variables
For this app, 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?
.
.
Code language: Swift (swift)
Drawing in the UIView
Having declared the UIView reference, we need to initialize an instance object and draw a blue square at a specific location on the screen. We also need to add boxView as a subview of the app’s main view object. These tasks only need to be performed once when the app first starts up, so a good option is within a new method to be called from the viewDidLoad method of the ViewController.swift file:
override func viewDidLoad() {
super.viewDidLoad()
initView()
}
func initView() {
let frameRect = CGRect(x: 20, y: 20, width: 45, height: 45)
boxView = UIView(frame: frameRect)
if let view = boxView {
view.backgroundColor = UIColor.blue
self.view.addSubview(view)
}
}
Code language: Swift (swift)
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 17 Multitouch, Taps, and Gestures. For 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 * .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()
}
}
Code language: Swift (swift)
Before compiling and running the app, 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))
Code language: Swift (swift)
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)
Code language: Swift (swift)
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)
Code language: Swift (swift)
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 * .pi / 180))
self.boxView?.transform = scaleTrans.concatenating(rotateTrans)
Code language: Swift (swift)
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
Code language: Swift (swift)
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
Code language: Swift (swift)
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
}
Code language: Swift (swift)
After the animations have been added to the animation object, the animation sequence is started:
animator.startAnimation()
Code language: Swift (swift)
Once the touchesEnded method has been implemented, it is time to try out the app.
Building and Running the Animation App
Once all the code changes have been made and saved, click on the run button in the Xcode toolbar. Once the app has compiled, it will load into the iOS Simulator or connected iOS device.
When the app loads, the blue square should appear near the top left-hand corner of the screen. Tap the screen and watch the box glide and rotate to the new location, the size and color of the box changing as it moves:
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))
.
.
Code language: Swift (swift)
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 apps. 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.