'What is best practice for creating a reusable custom button views?

I have three buttons below that have the same UI, the only differences are the text for the labels and tap gesture actions. It looks like this:

What is the best practice for creating a reusable custom button view based on this situation?

So far I tried using: (1) custom button class but had difficulty implementing a stack view where I can configure the two labels in the button, (2) UIButton extension but an issue where tapping the button caused the app to crash

class SetActivityVC: UIViewController {
 
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupViews()
    }
    
    lazy var firstButton: UIButton = {
        let button = UIButton()
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapFirst))
        button.addGestureRecognizer(tapGesture)
        
        button.setBackgroundImage(Image.setButtonBg, for: .normal)
        button.addShadowEffect()
        
        let label = UILabel()
        label.text = "No Exercise"
        label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        label.textColor = .black
        
        let subLabel = UILabel()
        subLabel.text = "no exercise or very infrequent"
        subLabel.font = UIFont.systemFont(ofSize: 12, weight: .regular)
        subLabel.textColor = .gray
        
        let stack = UIStackView(arrangedSubviews: [label, subLabel])
        stack.axis = .vertical
        stack.alignment = .center
        stack.isUserInteractionEnabled = true
        stack.addGestureRecognizer(tapGesture)
        
        button.addSubview(stack)
        stack.centerInSuperview()
        
        return button
    }()
    
    lazy var secondButton: UIButton = {
        let button = UIButton()
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapSecond))
        button.addGestureRecognizer(tapGesture)
        
        button.setBackgroundImage(Image.setButtonBg, for: .normal)
        button.addTarget(self, action: #selector(didTapSecond), for: .touchUpInside)
        button.addShadowEffect()
        
        let label = UILabel()
        label.text = "Light Exercise"
        label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        label.textColor = .black
        
        let subLabel = UILabel()
        subLabel.text = "some light cardio/weights a few times per week"
        subLabel.font = UIFont.systemFont(ofSize: 12, weight: .regular)
        subLabel.textColor = .gray
        
        let stack = UIStackView(arrangedSubviews: [label, subLabel])
        stack.axis = .vertical
        stack.alignment = .center
        
        button.addSubview(stack)
        stack.centerInSuperview()
        
        return button
    }()
    
    lazy var thirdButton: UIButton = {
        let button = UIButton()
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapThird))
        button.addGestureRecognizer(tapGesture)
        
        button.setBackgroundImage(Image.setButtonBg, for: .normal)
        button.addTarget(self, action: #selector(didTapSecond), for: .touchUpInside)
        button.addShadowEffect()
        
        let label = UILabel()
        label.text = "Moderate Exercise"
        label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        label.textColor = .black
        
        let subLabel = UILabel()
        subLabel.text = "lifting/cardio regularly but not super intense"
        subLabel.font = UIFont.systemFont(ofSize: 12, weight: .regular)
        subLabel.textColor = .gray
        
        let stack = UIStackView(arrangedSubviews: [label, subLabel])
        stack.axis = .vertical
        stack.alignment = .center
        
        button.addSubview(stack)
        stack.centerInSuperview()
        
        return button
    }()
    
    @objc func didTapFirst() {
        print("Tapped 1")
    }
    
    @objc func didTapSecond() {
        print("Tapped 2")
    }
    
    @objc func didTapThird() {
        print("Tapped 3")
    }
}

extension SetActivityVC {
    fileprivate func setupViews() {
        addViews()
        constrainViews()
    }
    
    fileprivate func addViews() {
        view.addSubview(firstButton)
        view.addSubview(secondButton)
        view.addSubview(thirdButton)
    }
    
    // Using TinyConstraints 
    fileprivate func constrainViews() {
        firstButton.centerXToSuperview()
        
        secondButton.centerXToSuperview()
        secondButton.topToBottom(of: firstButton, offset: screenHeight * 0.03)
        
        thirdButton.centerXToSuperview()
        thirdButton.topToBottom(of: secondButton, offset: screenHeight * 0.03)
    }
}


Solution 1:[1]

I'd suggest organizing your code by moving each relevant function to its own .gs file following this similar post.

Also for reference on how to manage and deploy your App Script project you can read more about it here on this documentation

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Diego Sanchez