iosswiftuibuttonibdesignableibinspectable

Custom UIButton @IBDesignable


I created a UIButton subclass that looks like a checkmark.

Here is the class:

import UIKit

@IBDesignable
class CheckedButton: UIButton {

    // MARK: - Properties
    @IBInspectable var checked: Bool = false {
        didSet {
            // Toggle the check/uncheck images
            updateImage()
        }
    }


    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        updateImage()
        self.addTarget(self, action: #selector(tapped), for: .touchUpInside)
    }

    private func updateImage() {
        let image = checked ? UIImage(named: "checked") : UIImage(named: "unchecked")
        self.setImage(image, for: .normal)
    }

    /// Called each time the button is tapped, and toggles the checked property
    @objc private func tapped() {
        checked = !checked
        print("New value: \(checked)")
    }  
}

Since I set the checkedproperty as @IBInspectable, I see it in IB : IBInspectable

The weird thing is:

uncheck

empty

As the class is marked @IBDesignable, I would expect the button appearance to update in IB according to the value set for this property in the inspector tab. Got a clue?


Solution

  • UIimage(named:) method uses main bundle but Interface Builder load resources in different way.

    Try this:

    UIImage(named: "checked", in: bundle, compatibleWith: nil)

     @IBDesignable
    
     class CheckedButton: UIButton {
    
     // MARK: - Properties
     @IBInspectable var checked: Bool = false {
         didSet {
             // Toggle the check/uncheck images
             updateImage()
         }
     }
    
    
     override init(frame: CGRect) {
         super.init(frame: frame)
         setup()
     }
    
     required init?(coder aDecoder: NSCoder) {
         super.init(coder: aDecoder)
         setup()
     }
    
     override func prepareForInterfaceBuilder() {
         super.prepareForInterfaceBuilder()
         setup()
     }
    
     internal func setup() {
         self.addTarget(self, action: #selector(tapped), for: .touchUpInside)
     }
    
     private func updateImage() {
         let bundle = Bundle(for: CheckedButton.self)
         let image = checked ? UIImage(named: "checked", in: bundle, compatibleWith:nil) : UIImage(named: "unchecked", in: bundle, compatibleWith:nil)
         self.setBackgroundImage(image, for: .normal)
     }
    
     /// Called each time the button is tapped, and toggles the checked property
     @objc private func tapped() {
         checked = !checked
         print("New value: \(checked)")
     }
    

    just like this