Home iOS & Swift Books iOS Animations by Tutorials

11
Animation Keys & Delegates Written by Marin Todorov

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

One of the tricky parts about UIKit animations and the corresponding closure syntax is that, if you aren’t using some of the new APIs like UIViewPropertyAnimator (which you will look into in later chapters), once you create and run a view animation you can’t pause it, stop it or access it in any way.

With Core Animation, however, you can easily inspect animations that are running on a layer and stop them if you need to. Furthermore, you can even set a delegate object on your animations and react to animation events. In contrast to the completion block you’ve seen in view animations, you can receive delegate callbacks for when an animation begins and ends (or is interrupted).

In this chapter you’ll continue to work with the Bahama Air login project and use animation delegates to make your animations more interactive.

Introducing Animation Delegates

CAAnimation and its subclass CABasicAnimation implement the delegate pattern and let you respond to animation events.

CAAnimationDelegate features two methods that you can implement if you need either or both of them:

func animationDidStart(_ anim: CAAnimation)
func animationDidStop(_ anim: CAAnimation, finished flag: Bool)

You can either open the starter project for this chapter, or carry on with your own project if you’ve completed the previous chapter and its challenges.

Your first task is to make each form element pulse once when it reaches its final position on the screen using animationDidStop().

Open ViewController.swift and add the following code to viewWillAppear(), just before the line where you add the animation to your heading layer:

flyRight.delegate = self

Then add the delegate method to the class in an extension to your view controller:

extension ViewController: CAAnimationDelegate {
  func animationDidStop(_ anim: CAAnimation, 
    finished flag: Bool) {
    print("animation did finish")
  }
}

This simply prints out a line to the console to show that you are, in fact, calling your delegate method.

Build and run your project; you should see the following output in the Xcode console:

Okay, so your delegates are being called — but how do you discern which animation stopped in the code above? Remember, you added the same animation to three different layers! The following section shows you how to manage this with key-value pairs.

Key-value Coding Compliance

The CAAnimation class and its subclasses are written in Objective-C and are key-value coding compliant, which means you can treat them like dictionaries and add new properties to them at run time.

flyRight.setValue("form", forKey: "name")
flyRight.setValue(heading.layer, forKey: "layer")
flyRight.setValue(username.layer, forKey: "layer")
flyRight.setValue(password.layer, forKey: "layer")

Switching on Key Values

Now that you have the keys set on your animation, you can check for them in the animation delegate methods.

guard let name = anim.value(forKey: "name") as? String else {
  return
}

if name == "form" {
  //form field found
}
let layer = anim.value(forKey: "layer") as? CALayer
anim.setValue(nil, forKey: "layer")

let pulse = CABasicAnimation(keyPath: "transform.scale")
pulse.fromValue = 1.25
pulse.toValue = 1.0
pulse.duration = 0.25
layer?.add(pulse, forKey: nil)

Animation Keys

let info = UILabel()
info.frame = CGRect(x: 0.0, y: loginButton.center.y + 60.0, width: view.frame.size.width, height: 30)
info.backgroundColor = .clear
info.font = UIFont(name: "HelveticaNeue", size: 12.0)
info.textAlignment = .center
info.textColor = .white
info.text = "Tap on a field and enter username and password"
view.insertSubview(info, belowSubview: loginButton)
let flyLeft = CABasicAnimation(keyPath: "position.x")
flyLeft.fromValue = info.layer.position.x +     
view.frame.size.width
flyLeft.toValue = info.layer.position.x
flyLeft.duration = 5.0
info.layer.add(flyLeft, forKey: "infoappear")

Adding a Second Layer Animation

You’ll now add a second animation to the label that will fade in the instructions while the label slides in. Add the following code to the end of viewDidAppear:

let fadeLabelIn = CABasicAnimation(keyPath: "opacity")
fadeLabelIn.fromValue = 0.2
fadeLabelIn.toValue = 1.0
fadeLabelIn.duration = 4.5
info.layer.add(fadeLabelIn, forKey: "fadein")

Identifying Running Animations

You’ll need to know when the user taps on either the username or password field. Add the following extension to the very bottom of ViewController.swift:

extension ViewController: UITextFieldDelegate {
  func textFieldDidBeginEditing(_ textField: UITextField) {
    guard let runningAnimations = info.layer.animationKeys() else {
      return
    }
    print(runningAnimations)
  }
}
username.delegate = self
password.delegate = self

info.layer.removeAnimation(forKey: "infoappear")

Key Points

  • You can set a delegate for your layer animations and be notified whenever the animations starts or finishes.
  • Animations are key-value coding compliant so you can attach arbitrary pieces of data to give you more information about the animation’s context.
  • The add(_, forKey:) API allows you to optionally specify a forKey string parameter to give the particular animation a name.

Challenges

Challenge 1

In this challenge, you’ll strengthen your knowledge of animation delegates and key-value coding by replacing the existing cloud animation with layer animations.

  func animateCloud(layer: CALayer) {

    //1
    let cloudSpeed = 60.0 / Double(view.layer.frame.size.width)
    let duration: TimeInterval = Double(
      view.layer.frame.size.width - layer.frame.origin.x) 
      * cloudSpeed
    
    //2
    let cloudMove = CABasicAnimation(keyPath: "position.x")
    cloudMove.duration = duration
    cloudMove.toValue = self.view.bounds.width +
      layer.bounds.width / 2
    cloudMove.delegate = self
    cloudMove.setValue("cloud", forKey: "name")
    cloudMove.setValue(layer, forKey: "layer")
    layer.add(cloudMove, forKey: nil)
  }
animateCloud(layer: cloud1.layer)
animateCloud(layer: cloud2.layer)
animateCloud(layer: cloud3.layer)
animateCloud(layer: cloud4.layer)

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

© 2022 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.