how to programmatically fake a touch event to a UIButton?
Asked Answered
W

9

201

I'm writing some unit tests and, because of the nature of this particular app, it's important that I get as high up the UI chain as possible. So, what I'd like to do is programmatically trigger a button-press, as if the user had pressed the button in the GUI.

(Yes, yes -- I could just call the IBAction selector but, again, the nature of this particular app makes it important that I fake the actual button press, such that the IBAction be called from the button, itself.)

What's the preferred method of doing this?

Wilder answered 26/10, 2010 at 22:56 Comment(4)
Could you explain why it's important that you fake the button touch rather than call the action method directly? There really is no practical difference.Earful
@Jasarien: well, because there IS a difference! ;) In my app, several buttons use the same action method, and the method parses how to behave based on the button's tag. So the unit test, in addition to testing one particular code-path through the method, (a) needs to test them all and (b) also verifies that the buttons are correctly wired. In addition, it nicely abstracts my unit-test suite, as I can define unit tests as lines in a text file with something like this: "1 + 2 = 3" where "3" is the expected ending display, and everything before it is which button to push.Wilder
well doesn't your action method have an (id)sender parameter? Couldn't you just call the method and pass the button as the sender?Earful
Because of the abstract nature of my test harness, I don't KNOW the method that needs to be called for a particular button. I suppose I could dig into the button and get it, but doing sendActionsForControlEvents: does all this for me, with less opportunity for error, and keeps the test abstract and fitting with the design.Wilder
W
487

It turns out that

[buttonObj sendActionsForControlEvents:UIControlEventTouchUpInside];

got me exactly what I needed, in this case.

EDIT: Don't forget to do this in the main thread, to get results similar to a user-press.


For Swift 3:

buttonObj.sendActions(for: .touchUpInside)
Wilder answered 27/10, 2010 at 13:56 Comment(5)
Unfortunately, neither of these will always work - they rely on the receiver being a UIControl, rather than eg having a tap gesture.Margetmargette
May i know how you set up your 'buttonObj'? How do you bind this to your UIButton? By ID?Rinaldo
@TanSiongZhe, bottonObj is the UIButton. Whatever your reference to your button -- be it IBOutlet, iVar or what have you -- calling [myButton sendActionsForControlEvents: ... will do the expected thing which, in my case, was the desired thing.Wilder
@ChenLiYong, One reason would be if the myActionMethod: is not public, and so you can't call it directly.Menstruum
Xamarin: buttonObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);Radionuclide
J
52

An update to this answer for Swift

buttonObj.sendActionsForControlEvents(.TouchUpInside)

EDIT: Updated for Swift 3

buttonObj.sendActions(for: .touchUpInside)
Journey answered 1/2, 2015 at 0:49 Comment(1)
I don't think this will work if the button is created in a XCTestCase without a run loop supporting.Rothmuller
G
7

Swift 3:

self.btn.sendActions(for: .touchUpInside)
Gusto answered 15/2, 2017 at 6:41 Comment(1)
This is just repeating the higher scored answers.Footlambert
R
6

If you want to do this kind of testing, you’ll love the UI Automation support in iOS 4. You can write JavaScript to simulate button presses, etc. fairly easily, though the documentation (especially the getting-started part) is a bit sparse.

Randirandie answered 27/10, 2010 at 0:27 Comment(1)
I understand Instruments, Jeff -- what I'm saying is: I want my unit-tests to run in the device in my pocket, without a laptop or XCode or Instruments handy. Or do you know the secret of running instruments on the device? ;)Wilder
C
5

In this case, UIButton is derived from UIControl. This works for object derived from UIControl.

I wanted to reuse "UIBarButtonItem" action on specific use case. Here, UIBarButtonItem doesn't offer method sendActionsForControlEvents:

But luckily, UIBarButtonItem has properties for target & action.

 if(notHappy){        
         SEL exit = self.navigationItem.rightBarButtonItem.action;
         id  world = self.navigationItem.rightBarButtonItem.target;
         [world performSelector:exit];
 }

Here, rightBarButtonItem is of type UIBarButtonItem.

Countershaft answered 28/6, 2012 at 11:44 Comment(1)
if you use UIBarButtonItem, you can just [self doAction:self.navigationItem.rightBarButtonItem]; //-doAction: is self.navigationItem.rightBarButtonItem's toggle action name.Mccoy
S
5

For Xamarin iOS

btnObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);

Reference

Scrophulariaceous answered 14/2, 2017 at 13:6 Comment(0)
R
4

Swift 5:

class ViewController: UIViewController {
    @IBOutlet weak var theTextfield: UITextField!
    @IBOutlet weak var someButton: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        theTextfield.text = "Pwd"
        someButton.sendActions(for: .touchUpInside)
    }

    @IBAction func someButtonTap(_ sender: UIButton) {
        print("button tapped")
    }
}
Russellrusset answered 17/3, 2021 at 11:4 Comment(0)
I
2

It's handy for people who write Unit Tests without UI Tests ;-)

Swift 5 way to solve it for UIBarButtonItem, which does not have sendAction method like UIButton etc.

extension UIBarButtonItem {
    func sendAction() {
        guard let myTarget = target else { return }
        guard let myAction = action else { return }
        let control: UIControl = UIControl()
        control.sendAction(myAction, to: myTarget, for: nil)
    }
}

And now you can simply:

let action = UIBarButtonItem(title: "title", style: .done, target: self, action: #selector(doSomething))
action.sendAction()
Inconsequent answered 20/7, 2020 at 10:21 Comment(0)
B
-4

Swift 4:

self .yourButton(self)

Buffon answered 22/8, 2019 at 19:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.