2

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:

  • if I let this property as default, it is correctly showing in the storyboard

uncheck

  • but if I choose either on or off inthe inspector, the screen is not updated properly.

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?

Frederic Adda
  • 5,905
  • 4
  • 56
  • 71

2 Answers2

4

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

ShaoJen Chen
  • 690
  • 6
  • 10
-1

My suggestion is in using other custom function for getting image from assets:

extension UIImage {
    public static func image(name: String, _ sender: UIView?) -> UIImage? {
        #if TARGET_INTERFACE_BUILDER
            if let sender = sender {
                let bundle = Bundle(for: sender.classForCoder)
                return UIImage(named: name, in: bundle, compatibleWith: sender.traitCollection)
            } else {
                return UIImage(named: name)
            }
        #else
            return UIImage(named: name)
        #endif
    }
}

For your example:

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

Also, UIButton class has checked state, which you can use without creation own sub class:

let btn = UIButton(type: .custom)
btn.setImage(UIImage.image(name: "unchecked", self), for: .normal)
btn.setImage(UIImage.image(name: "checked", self), for: .selected)

btn.isSelected = true // change button state between checked and unchecked
AleX
  • 167
  • 4
  • Thanks for your answer. However, your extension looks quite complicated to get a couple of images that are in the assets library (which is the recommended way by Apple nowadays). Also, I need to subclass this button because I'll be using it multiple times, and I don't want to do the same image setting over and over. – Frederic Adda Nov 18 '17 at 07:22