CALayer And Auto Layout With Swift
Are you using CALayer sublayers and are you complaining about the missing auto layout? Well, let’s try some workarounds.
Recently, I had to work with
CALayer to make some effects in an iOS Application. Unfortunately, I had some problems with the auto layout and I had to find a workaround. In this article, I propose some approaches which I tried. You can find the best one at the end of this article in “Final Comparison”.
This article is not supposed to be a guide for
CALayer but it’s just an explanation of a specific scenario: a workaround for the missing auto layout in sublayers.
What Is A CALayer?
We can consider
CALayer a graphic context of any
UIView object where we can add corners radius, borders, shadows and so on. Then, we can also apply animations to some layer properties to get nice effects—like a corner radius animation when we highlight a button.
Core animation provides several layer types by default, the main ones are:
CALayer: it’s the base class which we can extend to create our custom layers.
CATextLayer: it’s a layer which provides the possibility to render a text from a string with the possibility to set some attributes.
CAShapeLayer: it’s a layer which provides the possibility to draw shapes using a
CAGradientLayer: it’s a layer which provides the possibility to create a color gradient using an array of
What About Auto Layout?
As we saw previously, a layer is a context of an
UIView object. It means that any
UIView object has a main layer which we can use to change its corner radius, border and so on. We don’t need to set any constraint to this main layer, since it fills automatically its view—we cannot change the frame of this layer manually since it will always fill its view.
At this point, you may be wondering: why should we bother about constraints if the main layer doesn’t need auto layout? Well, let’s consider that we want to use a sublayer in our view to add an additional text, shape or gradient with a specific frame. Unfortunately, iOS doesn’t allow the usage of constraints for sublayers. This means that we need a workaround to replace the missing auto layout.
We can use 3 approaches to achieve our goals. To simplify our code, we use a gradient layer which fills the parent view.
Please note that we can add a sublayer with whatever frame we want. We have just to set the
frame. In this example the sublayer fills its parent view to keep the example easy to understand.
In this approach, we add the new gradient layer and set its frame to the parent view
bounds. Then, to keep the frame updated we must use the callback which says that the view layout has been update. If we are inside an
UIViewController we can use
In the following example, we use the implementation inside an
Update With KVO
The second approach is using KVO to observe the parent view
bounds changes, we manually update the layer frame to fill its parent view:
Remember to remove the observer when you use KVO.
The last approach is using a custom
We know that the main layer of any
UIView fills automatically its view, this means that, if we have a view with a specific frame, we are sure that we have also its main layer at that frame. We perfectly know that when we apply the constraints to a subview, the latter has the frame automatically updated to satisfy the constraints. At this point, we can deduce that, if we set proper constraints to a subview, we’ll have also its main layer at the specific frame since the layer is always at the same frame of its view.
The problem of this approach is that we must replace every sublayers with a custom
UIView. On the other hand, in this way we can take advantage of the constraints used for this custom view which will be applied automatically also to its main layer.
For this approach, we must create a custom
We said that the view has a main layer which is a
CALayer by default. For our custom view we want a main layer of type
CAGradientLayer, therefore we must override the
layerClass property which says the type of the main layer:
If we don’t override it, the main layer will be always
CALayer without any way to change its type.
And, finally, we can set our gradient in
At the end, the class should be like this:
At this point, we have 3 valid workarounds which we can use. The next step is testing the performance to understand what is the best approach. To compare them, we can create a sample project where we use all of them and check how they behave.
If you don’t want to test by yourself, you can watch the following video with the final result:
In this sample app, I added three sublayers and rotated the simulator—with slow animations enabled—to check how the layers behave when the parent view
As we can notice in the video, the approach with the custom
UIView performs better than the other ones. The reason is that we are relying on the auto layout applied to the view instead of updating the sublayer frame manually. Therefore, creating a custom
UIView is an acceptable trade-off to obtain the best result so far.
We have just seen some workarounds for the auto layout and the best approach to use. Unfortunately, it’s just a workaround and not an official approach. This means that we may find an approach which may be better than the custom
UIView. For this reason, if you have a better approach, feel free to leave a comment with a description of your solution. I would be more than happy to discuss alternatives. Thank you.