If you haven’t seen code like this before try using it with any UI component in your app and check what it does. It adds a 1pt black border to the view and round its corners by 8pt.
Every UIView is backed with a layer which can be accessed with the view.layer. The layer property points to an instance of either the CALayer class or any of its subclasses like CAShapeLayer, CAGradientLayer, CATextLayer etc. Layers are part of the Core Animation library and are extensively used in custom drawing with Core Graphics and Core Animation, these two frameworks make iOS apps beautiful. The image below shows where these two frameworks lie in the iOS graphics drawing engine:
OpenGL, a low-level API which interacts directly with the GPU, does all the heavy lifting work of graphics processing. To make our lives even easier Apple has built Core Animation, a high-level wrapper above OpenGL so that we don’t need to write the low-level C code.
Now, since we have been introduced to CALayer and have access to the low-level graphics rendering engine lets start interacting with them and create some beautiful graphics.
There are various CALayer subclasses, below are the direct CALayer subclasses available in the Core Animation library:
In this part of the series we will build:
A loading indicator with CAShapeLayer and CAKeyFrameAnimation
A mirror reflection of the Haptik logo with CAReplicatorLayer
With CAShapeLayer we can easily draw curved paths and geometrical shapes. CAShapeLayer is mostly used in drawing custom UI components. At Haptik, we design most of the custom UIButton subclasses with CAShapeLayer.
But why would you write code to draw a circle or a triangle or anything complex when you have images?
There are a few reasons to write code:
Almost all properties of shape layers are animatable, which gives us the freedom to change these shapes with code at runtime, which we’ll learn how to do.
CAShapeLayer is vector based, thus they are resolution independent.
This can be drawn directly using the GPU so we can free the CPU for other tasks.
By the end of this section, we’ll be able to show this cool animation on the screen:
The animation above has three components:
A rounded rectangle added on the view layer
A gray color circle added on the rectangle layer
A dark gray color arc added on the circle layer
The below code draws the rounded rectangle on the screen:
To create the path of the shape layer we used a UIKit class UIBezierPath to skip the complexity of drawing paths with Core Graphics. The fillColor property of the shape layer fills the closed region of the layer with given color.
For drawing a circle we need to pass a startAngle and an endAngle. With these angles, we tell the system from where the path should start and where it should be drawn till. If we were drawing this circle with a pen, consider the strokeColor as the ink color and the lineWidth as the minimum width of the line that can be drawn with the pen. Changing the position of the layer centers it in the rectangle.
To add the arc we will again use the same function for drawing a circle, but we will pass different start angles and end angles to draw it as an arc:
The lineCap determines how the endpoints of the drawn curve are stroked.
To create a rotational animation in x-y plane we need to change the rotation transform along the z-axis and fortunately, we can easily do this with Core Animation.
With CAKeyFrameAnimation we can control the animation attributes like fromValue and toValue, timingFunction, calculationMode for different time intervals in the complete animation. The values array determines fromValue and toValue of the animatable property ( transform.rotation.z) in the time intervals given to the keyTimes array. The timing functions decide how the animations start and end.
You can find the complete code till this section on this CALayers-GitHub repo.
CAReplicatorLayer
CAReplicatorLayer is a container layer, it replicates the content added to it. It has some cool properties which can be used to instruct the container how the replication has to be done. Beautiful effects can be achieved by applying animations to the replicated content. Every contained content is called an instance. To show the usage of this layer we will create a reflection of an image. By the end of this section, we’ll be able to show the reflection of the Haptik logo like this:
Let’s build this!
First, we need a CAReplicatorLayer instance and on this instance, we’ll be adding an image layer of which the reflection we will be showing:
// Create a CAReplicatorLayerCAReplicatorLayer*replicatorLayer=[CAReplicatorLayerlayer];// Create the image layerUIImage*image=[UIImageimageNamed:@"haptik_logo"];CALayer*imageLayer=[CALayerlayer];imageLayer.contents=(__bridgeid)[imageCGImage];imageLayer.bounds=CGRectMake(0.0,0.0,image.size.width,image.size.height);imageLayer.anchorPoint=CGPointMake(0,0);// Set bounds of replicator layer// to height twice of image heightreplicatorLayer.bounds=CGRectMake(0.0,0.0,image.size.width,image.size.height*2);replicatorLayer.masksToBounds=YES;replicatorLayer.anchorPoint=CGPointMake(0.5,0.0);replicatorLayer.position=CGPointMake(self.view.frame.size.width/2.0,80.0);[replicatorLayeraddSublayer:imageLayer];
This code is pretty straight forward, the anchorPoint of a layer is the point from where all the geometric manipulations will happen. The default anchorPoint is (0.5, 0.5) which represents the center of the layer. We want to apply a rotation from the top of the layer, so we changed it to (0,0)`.
With the above code, we have added an image to the replicator layer and set its correct bounds. To get the reflection we need to apply a rotation transform and translate the replicated layer to the correct position as below:
The instanceTransform property of the replicator layer allows us to set the calculated transform on the replicated content. There are other properties of the replicator layer like instanceDelay, instanceColor which can be manipulated to get more control.
Setting the instanceCount to 2 instructs the replicator layer to create exactly two instances of the added content.
This is it! Running this code will give us the below output:
But this is not what you expected, yes because the mirror we used earlier was blurred and so was the reflection. But if that is what you also need then add a gradient layer to your layer as shown below:
At Haptik, we have used the CAReplicatorLayer to create a new typing indicator. This is how it looks!
If you want to download and run this code check the Github repo. And yes, Craig Federighi was online. ;)
CATextLayer
Text layers are used to layout and render plain and attributed strings, but we do this usually with UILabel. One amazing usage of CATextLayer is to mask UIView. In this section we will redesign the Haptik logo as in the image below:
We create a UIImageView with a pattern image and mask that pattern with the text layer:
// Create the imageViewUIImage*haptikLogo=[UIImageimageNamed:@"Artboard"];UIImageView*imageView=[[UIImageViewalloc]initWithFrame:CGRectMake(0,0,300,300)];imageView.image=haptikLogo;// Create the CATextLayer instance.CATextLayer*textLayer=[CATextLayerlayer];textLayer.frame=imageView.bounds;textLayer.rasterizationScale=[UIScreenmainScreen].scale;textLayer.contentsScale=[UIScreenmainScreen].scale;
Never forget to set the rasterizationScale and contentsScale, without these properties you might get blurry or smaller text depending on the screen resolution of the devices your app runs on.
Set whatever string you want to display as a mask with the desired font: