Displaying Animation in iOS

Animation can make your user interface visually interesting to look at while also emphasizing any important parts of the user interface.

Animation can involve one or more of the following:

  • Moving an item from one location to another
  • Resizing an item
  • Changing transparency
  • Rotating an item

To create basic animation, we need to use this code:

UIView.animate(withDuration: 2.0) {
   // animation code here
}

User interface objects such as buttons and labels are based on the UIControl class, which is based on UIView. So ultimately any user interface object can be animated as a UIView. The numeric value defines how long to make the animation run measured in seconds such as 2.0 seconds. The code inside the curly brackets then provides the actual animation.

Moving Items with Animation

To move an item, you need to define its starting and ending location. You can define the ending location of an item by visually placing it on the user interface. Then you can define the starting location through Swift code. Once you know where an item starts and ends up, you can define how long you want the movement animation to last.

For this example, we want to animate items as soon as the user interface loads. That means we need to define the initial location before the user interface loads. To do this, we’ll need to specify the initial location in the viewWillAppear method, which runs right before the user interface appears on the screen.

Click the Library icon and drag and drop a label, a text field, and an image view onto the user interface. Resize the text field and label.

Click the Background popup menu of the image view and choose a color such as orange. This will make the image view easy to see when it moves.

Click the Background popup menu of the View (in the Document Outline) and choose a color such as yellow. This will make it easier to see the label and text field against a colored background.

Add and connect (move the mouse pointer over a view, hold down the Control key, and Ctrl-drag under the class ViewController line in the ViewController.swift file) outlets.

@IBOutlet var myLabel: UILabel!
@IBOutlet var myTextField: UITextField!
@IBOutlet var myImageView: UIImageView!

Add the following viewWillAppear method:

override func viewWillAppear(_ animated: Bool) { 
    myLabel.center.x -= view.bounds.width 
    myTextField.center.x -= view.bounds.width 
    myImageView.center.x -= view.bounds.width
}

Right before the view appears on the screen, this code moves the label, text field, and image view to the left the exact width of the entire view. This essentially hides the label, text field, and image view from sight.

Edit the viewDidLoad method as follows:

override func viewDidLoad() { 
    super.viewDidLoad()
    UIView.animate(withDuration: 2.0) { 
        self.myLabel.center.x += self.view.bounds.width 
        self.myTextField.center.x += self.view.bounds.width 
        self.myImageView.center.x += self.view.bounds.width
}

The viewWillAppear method moved the label, text field, and image view off to the left by the width of the view (which will change depending on the iOS device the app runs on). The viewDidLoad method now uses the UIView.animate method to move the label, text field, and image view to the right by the width of the view. This animation takes 2.0 seconds.

Customizing Animation with Delays and Options

Rather than have multiple items move at the same time, you may want them to move individually. To do this, you need to introduce a delay for one or more animations, so rather than starting immediately, an animation may wait a fixed amount of time (such as 0.25 or 2.8 seconds) before running. By delaying animation, you can let one item animate before another starts, or stagger animation among multiple items so they start and finish animating at different times.

Normally animation runs just once and then stops. However, you can define two additional options that cause the animation to repeat indefinitely or to run forward and backward while repeating indefinitely. This can be useful to display animation to attract the user’s attention.

The modified UIView.animate command to include delays and options looks like this:

UIView.animate(withDuration: 3.4, delay: 2.3, options: [.repeat,
    .autoreverse], animations: {
    // animate code here
}, completion: nil)

Customizing Animation with Damping and Velocity

Another way to modify the movement of animated objects is to define a velocity and a damping ratio. The velocity defines how fast an object moves, measured in seconds. Higher values create faster movement, while lower values create slower movement.

The damping ratio creates a "spring" effect that makes a moving object appear to oscillate. A value of 1.0 creates no oscillation, while values closer to 0 create much greater oscillation.

The modified UIView.animate command to include velocity and damping looks like this:

UIView.animate(withDuration: 2.0, delay: 0.5,
        usingSpringWithDamping: 0.1, initialSpringVelocity: 0.5, options:
        [.repeat, .autoreverse], animations: {
            // animate code here
}, completion: nil)

Add the following in the viewDidLoad method to add damping and velocity:

UIView.animate(withDuration: 2.0, delay: 0.5,
   usingSpringWithDamping: 0.75, initialSpringVelocity: 0.2, options:
   [.repeat, .autoreverse], animations: {
        self.myTextField.center.x += self.view.bounds.width 
}, completion: nil)

Add the following in the viewDidLoad method to see how different damping and velocity values affect the animation:

UIView.animate(withDuration: 2.0, delay: 0.5,
   usingSpringWithDamping: 0.1, initialSpringVelocity: 0.5, options:
   [.repeat, .autoreverse], animations: {
        self.myImageView.center.x += self.view.bounds.width 
}, completion: nil)

Resizing Items with Animation

Besides moving an item from one location to another, you can also resize an item by changing its width, height, or both its width and height. To change a user interface object’s width or height, you need to specify the IBOutlet name of the object you want to resize and then specify a width or height value change such as

IBOutletName.frame.size.width += value
IBOutletName.frame.size.height += value

Edit the viewDidLoad method

UIView.animate(withDuration: 2.0, delay: 0.0, options:
       [.repeat, .autoreverse], animations: {
    self.myLabel.frame.size.width += 25
    self.myLabel.frame.size.height += 25 
}, completion: nil)

Rotating Items with Animation

Rotating an item involves defining a rotation angle using the transform property and the CGAffineTransform command as follows:

IBOutletName.transform = CGAffineTransform(rotationAngle: value)

The CGAffineTransform command rotates items by radians, so if you’re more comfortable specifying angles in degrees, we need to convert degrees into radians using a command from the GLKit framework like this:

import GLKit

GLKMathDegreesToRadians(45)

Edit the viewDidLoad method as follows:

let rotateMe = GLKMathDegreesToRadians(45)

UIView.animate(withDuration: 2.5, delay: 1.5, options:
    [.repeat, .autoreverse], animations: {
    self.myImageView.transform = CGAffineTransform(rotationAngle: CGFloat(rotateMe)) 
}, completion: nil)

Changing Transparency with Animation

Rather than move or rotate an item, you might want to change its appearance instead. One way to do this is to change the transparency of an object. This can make an object gradually disappear and reappear again.

Edit the viewDidLoad method as follows:

UIView.animate(withDuration: 2.0, delay: 0.0, options:
       [.repeat, .autoreverse], animations: {
    self.myLabel.alpha = 0.0
    self.myLabel.backgroundColor = UIColor.lightGray 
}, completion: nil)

Animating Transitions Between View Controllers

When an app has multiple views, it needs a way to switch from one view controller to another. By using a navigation or tab bar controller, you can get a simple animation that slides one view controller over the other, but you can also create your own animation for transitions between view controllers.

Click the Library icon and drag and drop a button anywhere on the user interface. Double-click this button, type Show, and press Enter.

Move the mouse pointer over the button, hold down the Control key, and Ctrl-drag above the last curly bracket at the bottom of the ViewController.swift file. Release the Control key and the left mouse button. A popup window appears. Click in the Name text field, type openView, click the Type popup menu and choose UIButton, and click the Connect button.

Click the Library icon and drag and drop a View Controller in the storyboard.

Move the mouse pointer over the yellow circle at the top of the first view controller, hold down the Control key, and Ctrl-drag anywhere over the second view controller. Release the Control key and the left mouse button. A popup window appears. Choose Custom. Xcode draws a segue connecting the two view controllers.

Click the Library icon and drag and drop a button on the second view controller. Double-click this button, type Hide, and press Enter.

Create new SecondViewController class that extends from UIViewController. Connect created class to storyboard using Identity Inspector. Add following snippet to SecondViewController and connect with Hide button.

@IBAction func dismissButton(_ sender: UIButton) { 
    dismiss(animated: true, completion: nil)
}

Let's create new class that will extends UIStoryboardSegue. Click File > New > File. A template dialog appears. Choose Cocoa Touch Class under the iOS category and click the Next button. Click in the Class text field and type CustomSegue. Click the Subclass of popup menu and choose UIStoryboardSegue.

Click the Main.storyboard file in the Navigator pane. Click Custom segue to "View Controller" in the Document Outline. Click the Attributes Inspector icon in the upper right corner of the Xcode window. Click in the Identifier text field and type custom (This can be any arbitrary text). Click the Class popup menu and choose CustomSegue.

Click the ViewController.swift file in the Navigator pane. Edit the openView IBAction method as follows:

@IBAction func openView(_ sender: UIButton) { 
    self.performSegue(withIdentifier: "custom", sender: self)
}

At this point, we’ve created the basic structure for defining an animated transition between the two view controllers. The final step involves defining this animation in the CustomSegue.swift file.

Creating a custom transition involves creating a segue between two view controllers and giving it a name. Then that segue needs its own .swift file that defines the actual animation between the two view controllers. This segue .swift file defines the starting and ending point of the animation. Some different ways to transition between view controllers include

  • Sliding the second view controller over the first from different angles
  • Scaling the second view controller so it appears to grow and cover the first view controller
  • Rotating the second view controller into place over the first view controller

When sliding the second view controller over the first, you need to define an x and y starting and ending point for the upper left corner of the second view controller. The origin (0,0) is defined by the upper left corner of the screen.

The starting point of the second view controller defines the upper left corner, which should place it off the screen. If you wanted the second view controller to slide into place from the bottom right corner, you would need to define its starting point at the bottom right corner like this:

secondVC.view.transform = CGAffineTransform(translationX: firstVC.view.
bounds.width, y: firstVC.view.bounds.height)

Then the ending point of the second view controller needs to be the origin (0,0) like this:

secondVC.view.transform = CGAffineTransform(translationX: 0.0, y: 0.0)

No matter what starting point you define for the second view controller, its ending point will always be the origin (0,0). By defining different values for the x and y starting point, you can make the second view controller slide into place from different angles

Scaling involves changing the width (x) and height (y) of the second view controller. A scaling value of 0 means the size of the second view controller is also zero, making the second view controller invisible. A scaling value of 1 means the size of the second view controller is its normal size. So animation involves defining a scaling value of 0 and an ending value of 1 like this:

secondVC.view.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
secondVC.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

Rotating means defining the starting rotation angle. The ending rotation angle is always 0. Since the rotation angle is measured in radians, we can use degrees and then convert those degrees into radians. The starting rotation angle can be any value you wish such as

let angle = GLKMathDegreesToRadians(125) 
secondVC.view.transform = CGAffineTransform(rotationAngle: CGFloat(angle))

Then the ending rotation angle is always 0 like this:

secondVC.view.transform = CGAffineTransform(rotationAngle: 0.0)

The entire CustomSegue.swift file should look like this:

import UIKit
import GLKit

class CustomSegue: UIStoryboardSegue {
    override func perform() {
        let firstVC = self.source
        let secondVC = self.destination
        firstVC.view.addSubview(secondVC.view)

        //secondVC.view.transform = CGAffineTransform(translationX: firstVC.view.bounds.width, 
            y: firstVC.view.bounds.height)
        //secondVC.view.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        let angle = GLKMathDegreesToRadians(125) 
        secondVC.view.transform = CGAffineTransform(rotationAngle: CGFloat(angle))

        //secondVC.view.alpha = 0

        UIView.animate(withDuration: 0.8, animations: {
                //secondVC.view.transform = CGAffineTransform(translationX: 0.0, y: 0.0) 
                //secondVC.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
                secondVC.view.transform = CGAffineTransform(rotationAngle: 0.0)
                //secondVC.view.alpha = 1
        }) { (finished) in
            firstVC.present(secondVC, animated: false, completion: nil)
        } 
    }
}

Rather than define your own animation transitions between view controllers, Xcode offers a Transition Style option that lets you choose how a view controller appears on the screen. The four different Transition Styles include

  • Cover Vertical. The view controller slides up from the bottom of the screen (default).
  • Flip Horizontal. The current view controller flips from right to left in 3D as if the new view controller were on back of the previous view controller.
  • Cross Dissolve. The current view controller fades out, while the new view controller fades in.
  • Partial Curl. The current view controller curls up from the bottom right corner like turning the page of a book, revealing the new view controller underneath.

Modify this openView IBAction method as follows:

@IBAction func openView(_ sender: UIButton) {
    let vc = self.storyboard?.instantiateViewController (withIdentifier: "second")
    present(vc!, animated: true, completion: nil)
}

The first line creates a vc constant that represents a view controller in the storyboard that has a StoryID called "second". Then the second line displays this view controller. That means we need to add a second view controller to the storyboard and give it a Storyboard ID of "second".

Click View (under the second View Controller) in the Document Outline. Choose View > Inspectors > Show Attributes Inspector. Click the Transition Style popup menu and choose Flip Horizontal.

Click the second View Controller Scene in the Document Outline or click the yellow circle icon at the top of this second view controller to select this newly added view controller. Choose View > Inspectors > Show Identity Inspector. Click in the Storyboard ID text field, type "second".

Click the first View Controller Scene in the Document Outline. Choose View > Inspectors > Show Attributes Inspector. Click the Transition Style popup menu and choose Flip Horizontal.