Effervescent CALayer Transfigurations on iOS
Could I have thought of a more pompous title for this post? I don’t think I could’ve. But if you have any suggestions, please let me know.
A week or-so ago, I was clicking around on Dribbble, which just happens to be one of my all-time favourite websites. I sometimes navigate there during those minutes wherein one waits with baited breath for a CI-build to succeed (or fail), and on that particular day I found a rather delightful loading-indicator animation in GIF-form which was featured on the front page. It looked something like this:
Alas, in the days since I have been unable to once again find the original animated GIF with which I fell so deeply in awe. It had such a profound impact on my curiosity, that I could picture it in my head as clearly as if I were staring at the image in my browser. I decided, after being momentarily frustrated over not being able to find it again, that I would “brain-dump” what I had either imagined or actually seen —I’m now uncertain whether I actually saw it — into a tutorial for the masses.
Needless to say that this tutorial is so loaded with such sweet GUI-sugar that it ought to be considered some kind of hazard — or at the very least dangerously indulgent. 👍
Part 1 — The shortcomings of CALayer
Go ahead and create a new, empty Xcode project. You should use the Single-view application template, and ensure that the language is set to Swift. Don’t pay attention to the checkboxes asking you to use Core Data or any of that hooey, because you won’t be needing them.
Now, create a new Swift file. Name it whatever you wish. This file should be a subclass of CALayer.
The first thing I’m going to add is a custom initialiser. This is because we’re wanting to supply the number of items for the stack, and it’s nicer to put this kind of thing in an initialiser. Furthermore, I’ll add a variable to hold the base colour, and one to hold the size of each stack item.
Whenever you insert sublayers into a parent — think of them as children and parents if that makes it easier on you —layer, they will get clipped during animation because the parent is only a certain size. That isn’t a pretty effect, and it ruins many animations. So I’ll disable that, too.
For the sake of keeping things brief — really, I could go on all day — I’ll add a loop into the init method we just wrote. Instead of having six lines describing my shape, I’ll put that into a function and make the loop more readable.
Here’s a sample I prepared earlier:
If you noticed, the colour variable we added is calling a method that we don’t have yet. Since we’re going to be messing with UIColor a little to set the brightness — or hue or saturation, whatever tickles your fancy really — of the shapes, save this for use later:
Trying to find different shades of the same colour using a colour wheel — lets be honest here — sucks unless you’re a graphic designer with a good eye. With this little extension, you can have a “base” colour, and create darker or lighter variants of it as required throughout your app.
With me so far?
Okay, so CALayer and CAShapeLayer are two quite powerful and confusing components of CoreAnimation. So I’ll give a quick rundown of what we’ve achieved so far.
- Created a subclass of CALayer
- Written a convenience initialiser, that takes the number of shapes to draw.
- Added a variable to hold the base colour, the item size and the size of the parent layer.
- Written a loop to create, position and translate each shape layer in the parent layer.
- Applied a fill colour to the layers.
- Reversed the sublayers array.
- Centred the parent layer in the superview.
- Adjusted the anchor point of each shape layer.
- Converted degrees into radians.
That point regarding the reversal of sublayers is important, because of the way we’re presenting the stack. Any runtime modification to it, such as adjusting the distance between each object on the Z axis, will break the effect we’re creating without the reversal first taking place.
CAShapeLayer is one type of CALayer to have a fill colour and path. We are modifying these properties to draw what we like on the screen.
Whenever we modify the anchor point of a layer, the layer’s frame will shift. This isn’t at all desirable for the effect we’re trying to achieve, and if you’ve ever tried modifying this property, chances are it wasn’t what you wanted either.
By default, a layer has an anchor point of 0,0, which means that all animation performed on it, will happen around that point. To achieve our goal, we must anchor the layer in its centre.
I wish we could centre a CALayer as easily as we can a UIView, but alas we cannot. — Me
Part 2 — Stamp your hind legs, get behind me! Animate!
Go ahead and paste this into your class, underneath the last squiggly bracket in your file.
You’ve just extended the SpinnyMcSpinface — sorry for that, by the way — class with extra functionality. One function starts the animation, one ends it, and the other one generates the CABasicAnimation that we apply to each child shape in the loop. 🎉
Before we run the app to see the animation working — it won’t, at the minute — make sure your main view controller file has the following lines in place:
If we run the app now you’ll see that the animation is taking place. 🎉
Each layer has a slightly different colour, creating a nice effect. But what’s wrong with this picture?
You can see that the layers are all positioned correctly, but the angle is wrong. So let’s correct that now. Add the following function:
And call that function right after centerInSuperlayer() in the initialiser, as follows:
What that will do, is rotate the parent layer 60 degrees along its X-axis. Once we run this, we should see the stack…
Part 3— The sheer brilliance of CATransformLayer
If you run the animation again after doing the above, it looks like this:
This is because CALayer itself doesn’t actually render depth. We’ve positioned everything to be beneath the other using zPosition, but as soon as you rotate it expecting to see a “stack,” you’ll be sorely disappointed.
You can however position items into a stack with ordinary CALayer, although a rotation similar to the above wouldn’t be possible. This is because any new transform applied on top of an existing one will remove the old.
That’s where CATransformLayer comes in. It’s a nifty little layer type that supports depth out of the box.
To finish, and achieve the effect desired, simply change your subclass from CALayer to CATransformLayer, and re-run the app. 🎉
At jtribe, we proudly craft software solutions for iOS, Android and Web and are passionate about our work. We’ve been working with iOS and Android platforms since day one and are one of the most experienced mobile development teams in Australia. We measure success on the impact we have, and with over six-million end users we know our work is meaningful, and that continues to be our driving force.