1

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

Nawabu
  • 37
  • 6
  • Did you try to `Instruments -> TimeProfiler`? – vpoltave Aug 13 '20 at 09:03
  • `layoutSubviews` is called frequently (tens of times a second). You shouldn't do any setup (definitely not setting images). If you want to setup something in there, it should be related to view size and you should check that the size really changed. Otherwise your changes will just cause a new layout. – Sulthan Aug 13 '20 at 09:18

2 Answers2

1

My custom button with some shadow and rounded corners, I use it directly within the Storyboard, no need to touch it programmatically.

class RoundedCornerButtonWithShadow: UIButton {
    override func awakeFromNib() {
        super.awakeFromNib()
        self.layer.masksToBounds = false
        self.layer.cornerRadius = self.frame.height/2
        self.layer.shadowColor = UIColor.black.cgColor
        self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
        self.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
        self.layer.shadowOpacity = 0.5
        self.layer.shadowRadius = 1.0
    }
}
Ronak Patel
  • 609
  • 4
  • 16
1

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).

Sulthan
  • 128,090
  • 22
  • 218
  • 270