DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • A Developer’s Guide to Multithreading and Swift Concurrency
  • Automate Developer Routine With Swift in iOS Development
  • Jakarta WebSocket Essentials: A Guide to Full-Duplex Communication in Java
  • How to Make a Picture-in-Picture Feature in iOS App Using AVFoundation

Trending

  • Build a Simple REST API Using Python Flask and SQLite (With Tests)
  • How to Create a Successful API Ecosystem
  • Event-Driven Microservices: How Kafka and RabbitMQ Power Scalable Systems
  • Apple and Anthropic Partner on AI-Powered Vibe-Coding Tool – Public Release TBD
  1. DZone
  2. Coding
  3. Frameworks
  4. CALayer and Auto Layout With Swift

CALayer and Auto Layout With Swift

This article presents three different methods as a workaround for the missing auto layout in sublayers when developing for iOS mobile apps.

By 
Marco Santarossa user avatar
Marco Santarossa
·
Jun. 28, 17 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
16.6K Views

Join the DZone community and get the full member experience.

Join For Free

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 I tried. You can find the best one at the end of this article.

This article is not supposed to be a guide for CALayer, just an explanation of a specific scenario: a workaround for the missing auto layout in sublayers.

Happy reading!

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 CGPath object.
  • CAGradientLayer: it’s a layer which provides the possibility to create a color gradient using an array of CGColorRef.

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 use 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 CALayer property frame. In this example, the sublayer fills its parent view to keep the example easy to understand.

Update in viewDidLayoutSubviews/layoutSubviews

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 updated. If we are inside an UIViewController we can use viewDidLayoutSubviews, otherwise layoutSubviews inside UIView.

In the following example, we use the implementation inside an UIViewController:

class ViewController: UIViewController {
 
    let gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [
            UIColor.red.cgColor,
            UIColor.green.cgColor
        ]
        return layer
    }()
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        view.layer.addSublayer(gradientLayer)
        gradientLayer.frame = view.bounds
    }
 
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
 
        gradientLayer.frame = view.bounds
    }
}

Update With KVO

The second approach is using KVO to observe the parent view bounds. When bounds changes, we manually update the layer frame to fill its parent view:

class ViewController: UIViewController {
 
    let gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
        layer.colors = [
            UIColor.yellow.cgColor,
            UIColor.brown.cgColor
        ]
        return layer
    }()
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        view.layer.addSublayer(gradientLayer)
        gradientLayer.frame = view.bounds
    }
 
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
 
        view.addObserver(self, forKeyPath: #keyPath(UIView.bounds), options: .new, context: nil)
    }
 
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
 
        view.removeObserver(self, forKeyPath: #keyPath(UIView.bounds))
    }
 
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if let objectView = object as? UIView,
            objectView === view,
            keyPath == #keyPath(UIView.bounds) {
            gradientLayer.frame = objectView.bounds
        }
    }
}

Remember to remove the observer when you use KVO.

Custom UIView

The last approach is using a custom UIView.

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 in the same frame of its view.

The problem with 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 UIView class:

class LayerContainerView: UIView

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:

override public class var layerClass: Swift.AnyClass {
    return CAGradientLayer.self
}

If we don’t override it, the main layer will always be CALayer without any way to change its type.

And, finally, we can set our gradient in awakeFromNib:

override func awakeFromNib() {
    super.awakeFromNib()
 
    guard let gradientLayer = self.layer as? CAGradientLayer else { return }
    gradientLayer.colors = [
        UIColor.blue.cgColor,
        UIColor.cyan.cgColor
    ]
}
 

At the end, the class should be like this:


class LayerContainerView: UIView {
 
    override public class var layerClass: Swift.AnyClass {
        return CAGradientLayer.self
    }
 
    override func awakeFromNib() {
        super.awakeFromNib()
 
        guard let gradientLayer = self.layer as? CAGradientLayer else { return }
        gradientLayer.colors = [
            UIColor.blue.cgColor,
            UIColor.cyan.cgColor
        ]
    }
}

Final Comparison

At this point, we have three 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 bounds changes.

Spoiler:

As seen 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.

Conclusion

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.

Frame (networking) Swift (programming language)

Published at DZone with permission of Marco Santarossa, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • A Developer’s Guide to Multithreading and Swift Concurrency
  • Automate Developer Routine With Swift in iOS Development
  • Jakarta WebSocket Essentials: A Guide to Full-Duplex Communication in Java
  • How to Make a Picture-in-Picture Feature in iOS App Using AVFoundation

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!