Chapters

Hide chapters

iOS Animations by Tutorials

Sixth Edition · iOS 13 · Swift 5.1 · Xcode 11

Section IV: Layer Animations

Section 4: 9 chapters
Show chapters Hide chapters

18. Replicating Animations
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.

In this chapter you’re going to try something completely new: using a container layer that lets you replicate animations.

Let me introduce you to my favorite layer class: CAReplicatorLayer.

The idea behind CAReplicatorLayer is simple. You create some content — it could be a shape, an image or anything else you can draw with layers — and CAReplicatorLayer makes copies of it on the screen, like so:

“Why would I need to clone shapes or images?” you would ask. And you would be right to ask that; it’s not often you’d need the exact appearance of anything cloned a number of times.

CAReplicatorLayer’s superpowers come from the fact you can easily instruct it to make each clone slightly different from its ancestor.

For example, you could progressively change the tint of each copy. Your original layer could be magenta, while you progress the tint towards cyan as you create each copy.

Furthermore, you can apply a transform between copies; for example, you can apply a simple rotation transform between each copy to draw them in a circle, as shown below:

But the best feature of all is the ability to set an animation delay to follow each copy. When you set an instanceDelay of 0.2 seconds and add an animation to your original content, the first copy will animate with a delay of 0.2 seconds, the second copy will animate in 0.4 seconds, the third one in 0.6 seconds and so forth.

You can use this to create engaging and complex animations where you animate multiple elements in a synchronous manner.

In this chapter, you’re going to work on a personal assistant app that will “listen” to your questions and answer back. As a wink to Apple’s own personal assistant Siri, your project has been named Iris.

You’re going to create two different replications. First, you’ll create the visual feedback animation that plays while Iris talks, which will look much like a psychedelic sine wave:

Then you’ll use CAReplicatorLayer to create an interactive microphone-driven audio wave, which will provide visual feedback while the user speaks:

These two animations will introduce you to many features of CAReplicatorLayer. To cover every feature this layer offers would fill an entire book on its own!

But you don’t need to listen to me yammer on about how much I like creating animations with CAReplicatorLayer; it’s time to experience the magic for yourself.

Replicating like rabbits

Starter project overview

Launch the starter project for this chapter and open Main.storyboard. You’ll notice that the project setup is quite straightforward:

Setting up the replicator layer

Open ViewController.swift and add the following two properties:

let replicator = CAReplicatorLayer()
let dot = CALayer()
let dotLength: CGFloat = 6.0
let dotOffset: CGFloat = 8.0
replicator.frame = view.bounds
view.layer.addSublayer(replicator)
dot.frame = CGRect(
  x: replicator.frame.size.width - dotLength,
  y: replicator.position.y,
  width: dotLength, height: dotLength)

dot.backgroundColor = UIColor.lightGray.cgColor
dot.borderColor = UIColor(white: 1.0, alpha: 1.0).cgColor
dot.borderWidth = 0.5
dot.cornerRadius = 1.5

replicator.addSublayer(dot)

replicator.instanceCount = Int(view.frame.size.width / dotOffset)

replicator.instanceTransform = CATransform3DMakeTranslation(
  -dotOffset, 0.0, 0.0)

Your first replicated animation

To understand what instanceDelay does, you’ll add a little test animation to dot. Add the following to the end of viewDidLoad():

// This is a test animation, you're going to delete it
let move = CABasicAnimation(keyPath: "position.y")
move.fromValue = dot.position.y
move.toValue = dot.position.y - 50.0
move.duration = 1.0
move.repeatCount = 10
dot.add(move, forKey: nil)
// This is the end of the code you're going to delete
replicator.instanceDelay = 0.02

Replicating multiple animations

CAReplicatorLayer replicates the content and animations you create in the manner you instruct. But it’s up to you to come up with some cool animations that would look even cooler when replicated.

Scale animation

First you’ll continuously scale the dot layer up and down to produce a wave of dots.

let scale = CABasicAnimation(keyPath: "transform")
scale.fromValue = NSValue(caTransform3D: CATransform3DIdentity)
scale.toValue = NSValue(caTransform3D: 
  CATransform3DMakeScale(1.4, 15, 1.0))
scale.duration = 0.33
scale.repeatCount = .infinity
scale.autoreverses = true
scale.timingFunction = CAMediaTimingFunction(name: .easeOut)
dot.add(scale, forKey: "dotScale")

Opacity animation

Next you’ll make the original dot layer fade in and out. This will make the wave some dimension and change the alpha as it grows and shrinks to simulate light conditions. It will look much like a spinning twisty ribbon candy:

let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 1.0
fade.toValue = 0.2
fade.duration = 0.33
fade.beginTime = CACurrentMediaTime() + 0.33
fade.repeatCount = .infinity
fade.autoreverses = true
fade.timingFunction = CAMediaTimingFunction(name: .easeOut)
dot.add(fade, forKey: "dotOpacity")

Tint animation

If you push your imagination (and squint a little) you can imagine the wave twisting around and around on your screen. That impression would be a lot more clear if you animated its tint, as if the wave had a different color on each side.

let tint = CABasicAnimation(keyPath: "backgroundColor")
tint.fromValue = UIColor.magenta.cgColor
tint.toValue = UIColor.cyan.cgColor
tint.duration = 0.66
tint.beginTime = CACurrentMediaTime() + 0.28
tint.fillMode = .backwards
tint.repeatCount = .infinity
tint.autoreverses = true
tint.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
dot.add(tint, forKey: "dotColor")

Animating CAReplicatorLayer properties

So far you’ve created a pretty dazzling effect by animating the content in your replicator layer. But since CAReplicatorLayer is a layer itself, you can animate a number of its own properties too.

let initialRotation = CABasicAnimation(keyPath: 
  "instanceTransform.rotation")
initialRotation.fromValue = 0.0
initialRotation.toValue   = 0.01
initialRotation.duration = 0.33
initialRotation.isRemovedOnCompletion = false
initialRotation.fillMode = .forwards
initialRotation.timingFunction = CAMediaTimingFunction(name: .easeOut)
replicator.add(initialRotation, forKey: "initialRotation")

let rotation = CABasicAnimation(keyPath: "instanceTransform.rotation")
rotation.fromValue = 0.01
rotation.toValue   = -0.01
rotation.duration = 0.99
rotation.beginTime = CACurrentMediaTime() + 0.33
rotation.repeatCount = .infinity
rotation.autoreverses = true
rotation.timingFunction = CAMediaTimingFunction(name:  .easeInEaseOut)
replicator.add(rotation, forKey: "replicatorRotation")

meterLabel.text = assistant.randomAnswer()
assistant.speak(meterLabel.text!, completion: endSpeaking)
speakButton.isHidden = true
replicator.removeAllAnimations()
let scale = CABasicAnimation(keyPath: "transform")
scale.toValue = NSValue(caTransform3D: CATransform3DIdentity)
scale.duration = 0.33
scale.isRemovedOnCompletion = false
scale.fillMode = .forwards
dot.add(scale, forKey: nil)
dot.removeAnimation(forKey: "dotColor")
dot.removeAnimation(forKey: "dotOpacity")
dot.backgroundColor = UIColor.lightGray.cgColor
speakButton.isHidden = false

Interactive replication animations

Right now you need to press the speak button each time to see and hear Iris’ answer. But you don’t get to actually ask her anything, which, to be honest, is the really fun part.

dot.backgroundColor = UIColor.green.cgColor
monitor.startMonitoringWithHandler { level in
  self.meterLabel.text = String(format: "%.2f db", level)
}
monitor.startMonitoringWithHandler { level in
  self.meterLabel.text = String(format: "%.2f db", level)
  let scaleFactor = max(0.2, CGFloat(level) + 50) / 2
}
var lastTransformScale: CGFloat = 0.0
let scale = CABasicAnimation(keyPath: "transform.scale.y")
scale.fromValue = self.lastTransformScale
scale.toValue = scaleFactor
scale.duration = 0.1
scale.isRemovedOnCompletion = false
scale.fillMode = .forwards
self.dot.add(scale, forKey: nil)
self.lastTransformScale = scaleFactor

monitor.stopMonitoring()
dot.removeAllAnimations()

Key points

  • You can easily create compound animation effects via CAReplicatorLayer to combine multiple copies of the same animation.
  • You set number and variations between the animation replications via the instanceCount, instanceTransform and instanceDelay properties on CAReplicatorLayer.
  • Besides animating properties on the original animation, you can animate also properties on the replicator layer itself.

Challenges

Challenge 1: Smooth the transition between microphone input and Iris animations

Your first challenge is to not just remove the two animations running on the dot layer by calling dot.removeAllAnimations(), but to animate the wave back to a state suitable for the next animation to be run.

Section conclusion

This wraps up the basic layer animations section. You’ve been through a lot — and learned a ton of things along the way!

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.
© 2024 Kodeco Inc.

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 Kodeco Personal Plan.

Unlock now