Search code examples
iosswiftautolayoutuikitcalayer

Swift: Is there a faster, more responsive way to implement different corner radii for a UIView?


Currently, I implement multiple corner radii on my bubbleView which is a UIView by doing something along the lines of:

// First create the bubble view
bubbleView = UIView()
bubbleView.layer.cornerRadius = 4 // set the corner radius of the "smaller" corner style
bubbleView.layer.cornerCurve = .continuous
bubbleView.clipsToBounds = true
bubbleView.backgroundColor = UIColor.systemBlue

// ...

// Update the "mask" of the bubble view to give another type of rounded corners
let maskPath = UIBezierPath(roundedRect:bubbleView.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: 17.0, height: 0.0))
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
bubbleView.layer.mask = maskLayer // updates the mask

My issue is I am setting the mask, self.bubbleView.layer.mask = maskLayer, of the bubbleView in the func layoutSubviews() function, which causes a noticable delay, for example, when the device rotates from portrait to landscape mode.

Is there a faster, more efficient way to implement different corner radii for a UIView that responds faster than simply updating the mask in layoutSubviews() ?


Solution

  • You could try using a subview with the larger radius corners...

    • custom view
    • clear background
    • all 4 corners set to Radius of 4
    • subView with desired background color
    • set Radius of 17 on desired corners of subView

    Here's some sample code:

    class MyCustomView: UIView {
    
        // self's background will be .clear
        //  so we use a custom property to set the
        //  background of the subView
        public var viewColor: UIColor = .clear {
            didSet {
                subView.backgroundColor = viewColor
            }
        }
        
        // corners to use larger radius
        public var corners: CACornerMask = [] {
            didSet {
                subView.layer.maskedCorners = corners
            }
        }
        
        public var smallRadius: CGFloat = 0 {
            didSet {
                layer.cornerRadius = smallRadius
            }
        }
    
        public var bigRadius: CGFloat = 0 {
            didSet {
                subView.layer.cornerRadius = bigRadius
            }
        }
        
        private let subView = UIView()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        
        func commonInit() -> Void {
            
            addSubview(subView)
            subView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                subView.topAnchor.constraint(equalTo: topAnchor),
                subView.leadingAnchor.constraint(equalTo: leadingAnchor),
                subView.trailingAnchor.constraint(equalTo: trailingAnchor),
                subView.bottomAnchor.constraint(equalTo: bottomAnchor),
            ])
    
            // round all 4 corners of self's layer with the small radius
            layer.masksToBounds = true
            layer.cornerRadius = smallRadius
            layer.cornerCurve = .continuous
    
            // subview only specified corners with bigger radius
            subView.layer.masksToBounds = true
            subView.layer.cornerRadius = bigRadius
            subView.layer.cornerCurve = .continuous
            subView.layer.maskedCorners = corners
    
        }
        
    }
    

    and a test view controller to demo it:

    class TestViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let v = MyCustomView()
    
            v.viewColor = .systemBlue
            v.smallRadius = 4
            v.bigRadius = 17
            
            // set top-left, top-right, bottom-left to use larger radius
            v.corners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
            
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
                v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
                v.heightAnchor.constraint(equalToConstant: 120.0),
                v.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            ])
        }
        
    }