Search code examples
iosswiftuibuttoncashapelayerviewwillappear

Load CAShapeLayer With UIButton on viewWillAppear Method


I have a custom button with some shadow effect, following class i am using to create a custom button which i got from stackoverflow. It users CAShapeLayer to give effect of shadow on button.

import UIKit

class CustomButton: UIButton {

var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
    super.layoutSubviews()

    if shadowLayer == nil {
        shadowLayer = CAShapeLayer()
        shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath

        shadowLayer.fillColor = UIColor.white.cgColor
        shadowLayer.shadowColor = UIColor.darkGray.cgColor
        shadowLayer.shadowPath = shadowLayer.path
        shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
        shadowLayer.shadowOpacity = 0.8
        shadowLayer.shadowRadius = 2

        layer.insertSublayer(shadowLayer, at: 0)
        //layer.insertSublayer(shadowLayer, below: nil) // also works
    }
}
}

Following is an image of screen where i have created 4 buttons using this CustomButton Class which works fine.

enter image description here

when any button from above four is clicked i changed it's following property so it can look like an active button.

// to make button active
    func setSelectedButton(sender:CustomButton){
        //sender.backgroundColor = UIColor.red
        sender.shadowLayer.fillColor = UIColor(named: "headerColor")?.cgColor
        sender.setTitleColor(UIColor.white, for: .normal)
    }
// to make button inactive 
    func setUnselected(sender:CustomButton){
        //sender.backgroundColor = UIColor.init(red: 80/255, green:101/255, blue: 161/255, alpha: 1)
        sender.shadowLayer.fillColor = UIColor.white.cgColor
        sender.setTitleColor(.black, for: .normal)
    }

Everything works fine. Now what i want is whenever view appears i want the first button to be selected by default which is routineButton to do that i have written following code in viewWillAppearMethod

override func viewWillAppear(_ animated: Bool) {
        self.navigationItem.title = navigationTitle
        self.view.makeToastActivity(.center)
        self.routineButton.sendActions(for: .touchUpInside) //default selection of routineButton
        loadEvaluationList(userId: 8, evaluationType: "RT") {
            self.view.hideToastActivity()
        }
    }

when self.routineButton.sendActions(for: .touchUpInside) executes it call setSelectedButton(sender:CustomButton) method which gives error at following line

 sender.shadowLayer.fillColor = UIColor(named: "headerColor")?.cgColor

which says shadowLayer is nil. This problem occur only when i try to set default selected button on viewWillAppear Method otherwise it works perfact. I think the problem occur because shadowLayer property of CustomButton is not initialised at time of viewWillAppear. so anyone knows what should i do? it will be helpful. Thank you in advance.


Solution

  • Move the initialization code for shadowLayer in the init methods, something like this:

    import UIKit
    
    class CustomButton: UIButton {
    
        var shadowLayer: CAShapeLayer!
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.initShadowLayer()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.initShadowLayer()
        }
    
        func initShadowLayer() {
            if shadowLayer == nil {
                shadowLayer = CAShapeLayer()
                shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
    
                shadowLayer.fillColor = UIColor.white.cgColor
                shadowLayer.shadowColor = UIColor.darkGray.cgColor
                shadowLayer.shadowPath = shadowLayer.path
                shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
                shadowLayer.shadowOpacity = 0.8
                shadowLayer.shadowRadius = 2
    
                layer.insertSublayer(shadowLayer, at: 0)
            }
    
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            // Reset the path because bounds could have been changed
            shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
            shadowLayer.shadowPath = shadowLayer.path
        }
    }
    

    viewWillAppear is called before layoutSubviews of the button and shadowLayer is not initialized yet. Also try not to call sendActions(for: .touchUpInside), instead call the setSelectedButton function if you just want to configure the button appearance.