Why does UIImageView.image need to be set using a DispatchQueue.main.async even on the main thread

Viewed 252

Trying to set an image on a button when tapped. Verified that the targetAction handler is always invoked on the main thread.

This doesn't work

@objc func buttonTapped() {
     self.mybutton.imageView?.image = MyObjcClass.getImageNamed("DarkVersion")
}

But this works:

@objc func buttonTapped() {
    DispatchQueue.main.async { // Does not work without this dispatch
        self.mybutton.imageView?.image = MyObjcClass.getImageNamed("DarkVersion")
    }
}

On the debugger I can see that both are on the main thread

2 Answers

Correct way to set an image to a button is using of setImage(_ image: UIImage?,for state: UIControl.State) method.
When you use direct access to set image, the button does not know about it and change the set image in state changing routine.
Really you do not need to change image in buton's action method, you just need set images for states and to change a state of the button in this method. Button'll pick appropriate image for state.

The correct code from the view controller should be:

class ViewController: UIViewController {
    @IBOutlet weak var myButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(buttonTapped))
        myButton.addGestureRecognizer(tapGesture)
    }

    @objc func buttonTapped() {
        self.myButton.setImage( MyObjcClass().getImageNamed("DarkVersion"), for: .normal)

    }
}

Which works without fail, the difference is that setImage is used to set the image.

Related