Search code examples
iosswiftuiviewautolayoutcalayer

Swift: How to subclass CALayer so it resizes automatically?


It is known that the main/root layer of a UIView, e.g., view.layer, will resize automatically to fill the frame of the UIView (however, a root layer's sublayers will not automatically resize).

After subclassing CALayer like:

class CustomLayer : CALayer {
    //...
}

What methods can I implement to "draw" content so that the layer will automatically resize if used as the root CALayer of a view?


The reason I ask this is because I am current calling methods such as:

UIBezierPath(roundedRect: self.view.frame, byRoundingCorners: [.topLeft], cornerRadii: CGSize(width: 17, height: 15))

To update the path of a CALayer, but it would be amazing to be able to create a custom CALayer that can act as a root layer for a UIView so I don't need to consistently update its path.

Does anyone have any advice for this? Ultimately I'm striving to have a layer that can support unique radii for each corner that will automatically fill the UIView and resize without having to consistently update the frame.


Solution

  • You can subclass UIView, override layerClass and return a CAShapeLayer:

    class Shape: UIView {
    
        override public class var layerClass: AnyClass { CAShapeLayer.self }
        var shapeLayer: CAShapeLayer { layer as! CAShapeLayer }
        
        private var bezierPath: UIBezierPath = UIBezierPath() { didSet { shapeLayer.path = bezierPath.cgPath } }
        
        var fillColor: UIColor = .green { didSet { shapeLayer.fillColor = fillColor.cgColor } }
        var strokeColor: UIColor = .blue { didSet { shapeLayer.strokeColor = strokeColor.cgColor } }
        var lineWidth: CGFloat = 2 { didSet { shapeLayer.lineWidth = lineWidth } }
        
        override func didMoveToSuperview() {
            updateShape()
            shapeLayer.fillColor = fillColor.cgColor
            shapeLayer.strokeColor = strokeColor.cgColor
            shapeLayer.lineWidth = lineWidth
        }
        func updateShape() {
            bezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft], cornerRadii: CGSize(width: 17, height: 15))
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            print(#function)
            updateShape()
        }
        override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
            super.traitCollectionDidChange(previousTraitCollection)
            print(#function)
            // you can change the color of your shape here if needed (dark/light mode)
        }
    }