18 Mar 20208 min read

Creating animations with UIKit and Core Animation

We, developers, have a lot of options when it comes to animations nowadays. There is plenty of libraries that can be used to easily integrate an animation prepared by a designer into our applications. The most popular (probably) is a Lottie which is available for both iOS and Android. It's also possible to use Lottie for many multi-platform solutions like Flutter or React Native which makes it super convenient. There is also Rive, a great web-based tool for animations that has a plugin for Flutter as well as an iOS framework (it hasn't been updated for a while but should still work as far as I know). Those tools are great but most developers are not designers and do not know or do not want to play with graphics outside the code.

We have also libraries like Hero or Spring that can help you make simple transitions and animations without using any external tools for designers. However, it is pretty often that we have to implement something completely custom and can't use any already created framework. Or maybe you are like me and do not like to add too many dependencies to your projects and rather want to implement such things yourself. Either the reason is, as I said in my previous article about iOS Interview Questions, no matter what it is good to know such things as you might ie. be asked about this during the interview.

There are a few methods that we can use while working with animations. Let's take a closer look at 3 of then: Core Animation, widely known UIView.animate and UIView's keyframes.

Core Animation

Core Animation is a framework designed by Apple and is available on all the platforms supported by the company. That means you can only learn it once and take advantage of it on either iOS, macOS, tvOS and watchOS. You might not be aware of it but even if you have never heard about this framework before, you are still using CoreAnimation for all your applications you create.

Core Animation framework is quite powerful, sadly it's not that convenient to use. It's not that hard either though, all you have to do is to configure a few animation parameters and attach it to the view's layer.

Here is an example of an animating view's cornerRadius that auto reserves and repeats infinitely. Those two parameters are optional and if not set, the animation will just play once.

let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.cornerRadius))
animation.fromValue = 0
animation.toValue = 16
animation.duration = 1
animation.autoreverses = true
animation.repeatCount = .infinity
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)

squareView.layer.add(animation, forKey: "squareView.cornerRadius")

There is a small trick when playing animation only once though. The layer will reset to its initial value which might be confusing. When googling for an answer you might find a solution that suggests using those 2 properties to make it not reset it's value to the initial one.

animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false

There is another trick with this solution though. The animation is never removed, as well as the actual value of cornerRadius is never set to the expected one. So we can either set the cornerRadius to the desired value right before playing the animation and thanks to the animation.fromValue = 0 it will animate just fine. Or, we can use a delegate animation.delegate = self and implement func animationDidStart(_ anim: CAAnimation) method where we will set it to correct value, like this:

extension MyViewController: CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        print("\(squareView.layer.cornerRadius)")
    }
}

It is also fairly easy to set multiple values and animate between them.

let animation = CAKeyframeAnimation(keyPath: #keyPath(CAShapeLayer.cornerRadius)
animation.values = [0, 50, 16, 50, 0]
animation.keyTimes = [0, 0.25, 0.5, 0.75, 1]
animation.duration = 3
animation.isAdditive = true

squareView.layer.add(animation, forKey: "squareView.cornerRadius")

The values and keyTimes elements count must match each other and the keyTimes contains a value between 0 and 1, where 0 is the beginning of the animation and 1 is its end.

There is also one cool class that might be handy - CASpringAnimation - it inherits from the CABasicAnimation and makes the spring animations way easier to achieve. Give it a try! You can achieve a lot of great effects using it together with UIViews or Core Graphics framework.

UIView.animate

You might never have a chance to use Core Animation directly but I am pretty sure - if you are not a complete beginner as iOS developer - you have heard of this animation method.

The Core Animation method I described above has one big disadvantage though... you can only animate a single property with it. You can animate multiple by creating a group...

let group = CAAnimationGroup()
group.animations = [...]

... but it's still a lot of code to write for such a simple thing. That's why we have a more convenient method which everyone is familiar with.

animate(withDuration duration: TimeInterval, animations: @escaping () -> Void)

as well as a few more animate methods on UIView class that have some more arguments and are more configurable. With those methods, we can easily animate any animatable property from within the same animation block (closure). Also, we do not have to worry about layer/view resetting to its initial value as we directly change the actual value.

To achieve the first example all we need to do is call one simple method:

UIView.animate(withDuration: 1) {
    self.squareView.layer.cornerRadius = 16
}

It gets "funnier" when we want to make several steps in our animation since it can quickly become a messy and unreadable, just take a look:

UIView.animate(withDuration: 0.5, animations: {
    self.squareView.layer.cornerRadius = 16
}) { _ in
    UIView.animate(withDuration: 0.5, animations: {
        self.squareView.layer.cornerRadius = 50
    }) { _ in
        UIView.animate(withDuration: 0.5, animations: {
            self.squareView.layer.cornerRadius = 16
        }) { _ in
            UIView.animate(withDuration: 0.5, animations: {
                self.squareView.layer.cornerRadius = 50
            }) { _ in
                UIView.animate(withDuration: 0.5, animations: {
                    self.squareView.layer.cornerRadius = 0
                })
            }
        }
    }
}

Also, it's way harder to manage the duration of the whole animation as we could have to calculate ourselves the fraction of duration for each step.

ADVERTISEMENT
Sponsor Ordinary Coding
Sponsor Ordinary Coding

If you are interested in advertising your application or service on my blog, get in touch with me by email ordinary.coding@protonmail.com. If you are an indie developer you can have your app advertised completely for free!

UIView Keyframes

And finally, the latest and least known addition to the UIKit framework - keyframes. It's been added a really long time ago (iOS 7) but still, I rarely see this in any project I have been involved in.

So let's take a look at a similar example:

UIView.animateKeyframes(withDuration: 3, delay: 0, options: [], animations: {
    UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25) {
        self.squareView.layer.cornerRadius = 50
    }

    UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25) {
        self.squareView.layer.cornerRadius = 16
    }    

    UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25) {
        self.squareView.layer.cornerRadius = 50
    }

    UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
        self.squareView.layer.cornerRadius = 0
    }
})

It's way cleaner and readable than the previous solution although it has its disadvantages. Not everyone likes to work with relative duration and there is no easy way to create Spring Animation using this method.

Conclusion

When it comes to animations, we have quite a lot of ways to integrate them into our applications. Which one to choose you might ask? It all depends on your experience and the requirements since not all methods are good for some outcomes. Also, each solution has its own advantages and disadvantages.

Going with a 3rd party framework might be the easiest to implement but ie. Lottie or Rive might require some design skills or a designer friend that has experience with animations and can help us out. Thankfully there is a lot of free Lottie and Rive animations that we could use. Those libraries might be good for really complex animations but if we need something simpler or we simply do not want to add an additional framework to our project, we might want to go with the native method.

When to choose Core Animation and when the well known UIView.animate? Let's be honest, most of the time you will want to implement your animation with the 2nd option or use UIView Keyframes for something a little bit more complex (although not too complex 🙈) since it's really convenient to use and it's just not worth over complicating it with Core Animation. CA might be still useful though if we run into troubles with UIKit method and if something seems impossible with UIView.animate it might be actually possible with Core Animation.

Thank you for reading! Got any questions? Feel free to poke me on Twitter.

Useful Links