iosswiftxcodeuibuttonibdesignable

Custom @IBDesignable UIButton crashes Xcode


I wanted to design a rounded button with a shadow for my iOS project in Swift. So I came up with the following custom button class:

import UIKit

@IBDesignable class MainButton: UIButton {
    private var shadowLayer: CAShapeLayer!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let image = createBackgroundImage()
        setBackgroundImage(image, for: UIControl.State.normal)
        clipsToBounds = true
        
        contentEdgeInsets = UIEdgeInsets(top: 5, left: 20, bottom: 5, right: 20)
        
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 3)
        layer.shadowOpacity = 0.2
        layer.shadowRadius = 6
    }
    
    func createBackgroundImage() -> UIImage {
        let rect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
        UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
        let color = UIColor.white
        color.setFill()
        UIBezierPath(roundedRect: rect, cornerRadius: frame.height * 0.5).addClip()
        color.setFill()
        UIRectFill(rect)
        let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }
}

As soon as I set this class for a button in my Storyboard the "IBDEsignablesAgent-iOS" process takes nearly 100% of my CPU and Xcode hangs it self. This behavior makes it near impossible to debug the problem correctly.

I'm quite certain I do something in the wrong order or wrong method. But I have no clue how to solve it. Hopefully someone here can point me the right direction.

Thanks, Jens


Solution

  • Simply check that the size of the view actually changed:

    private var lastSize: CGSize = .zero
    
    override func layoutSubviews() {
       super.layoutSubviews()
    
       guard frame.size != lastSize else { return }
    
       lastSize = frame.size
    
       ...
    }
    

    Creating shadows is slow and layoutSubviews is called tens of times every second (basically, once every frame).