how to programmatically fake a touch event to a UIButton? - ios

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?

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)

An update to this answer for Swift
buttonObj.sendActionsForControlEvents(.TouchUpInside)
EDIT: Updated for Swift 3
buttonObj.sendActions(for: .touchUpInside)

Swift 3:
self.btn.sendActions(for: .touchUpInside)

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.

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.

For Xamarin iOS
btnObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);
Reference

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")
}
}

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()

Swift 4:
self .yourButton(self)

Related

should I don't use addTarget of UIButton in viewDidLoad

My senior was reviewing my code and he found that I have used UIButton addTarget method like this
override func viewDidLoad() {
super.viewDidLoad()
self.btnAccount.addTarget(self, action: #selector(Accounts(_:)), for: .touchUpInside)
}
Now he is saying that you should not use addTarget in viewDidLoad it will take time(kind of memory management thing I didn't get it) to load view controller but I didn't find it relevant
that's why I am asking this question did I made some mistake by doing this should I always make actions
I didn't hear of that and even if it is true, you should never try to do premature optimization on your app. UIButton is a UIControl object, which follows an event-listener pattern, which is often implemented with a hashmap (NSDictionary in Objective-C) of targets ('aka' Listeners or Observers) and it is not very time-consuming operation.
I personally prefer to setup all UI component right at the beginning:
lazy var btnAccount: UIButton = {
let btn = UIButton
// setup button's appearance
btn.addTarget(self, action: #selector(Accounts(_:)), for: .touchUpInside)
return btn
}()
P.S. Please ask him about the source of the fact and let me know.

how to detect when user click lyft Button swift

We want Lyft button touch event because I am working in analytics, so, I need how many people choose Lyft but I can't put UIView click event. I try below code.
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.checkAction))
cell.lyftButton.addGestureRecognizer(gesture)
How can i achieve this?
You can directly assign a selector method to lyftButton e.g
lyftButton.addTarget(self, action: #selector(lyftButtonAction(_:)), for: .touchUpInside)
#objc
func lyftButtonAction(_sender: UIButton) {
//Do your action
}
To retrieve the LyftButton, you'll need to fetch the button inside the Lyft view, after retrieving it, I tried to add another target to it which was your 'checkAction' method, but for some reason it is not being called. One workaround solution is:
On Auto Layout, created a transparent button on top of the Lyft Button View, let's callet it 'Transparent Lyft Button': Example (I've embeded in another view because it was on a stackView);
On the code, retrieved the button with the above method, held it in a variable, let's call it 'requestLyftButton' and disabled it.
Created an IBAction for the 'Transparent Lyft Button' that triggers the method 'self.checkAction' that you've created and also calls requestLyftButton.sendActions(for: .touchUpInside), which triggers the original Lyft SDK action.
To Retrieve Lyft UIButton:
#IBOutlet weak var lyftButton: LyftButton!
#IBOutlet weak var transparentLyftButton: UIButton!
var requestLyftButton: UIButton?
func retrieveLyftButton(in view: UIView) {
for view in view.subviews {
if let lyftBtn = view as? UIButton {
lyftBtn.isEnabled = false
requestLyftButton = lyftBtn
} else {
retrieveLyftBtn(in: view)
}
}
}
transparentLyftButton IBAction to trigger your method + lyft sdk original action:
#IBAction func requestLyft(_ sender: UIButton) {
if let lyftBtn = requestLyftButton {
checkAction() // Your method
lyftBtn.sendActions(for: .touchUpInside)
}
}
I hope that you can understand what was done, if you have any questions, just let me know.

How can properly use 'sender' argument in #UIAction code Swift?

I'm relatively new to Swift programming and just learned from this article on Stack Overflow that I can't change UIButton text via the 'sender' argument in #IBAction code, but must setup an #IBOutlet from the button and use methods on the outlet variable. How broad of a rule is that? What methods are OK to apply to the 'sender' argument, and which are not?
I don't know what they're referring to in that post you've linked to, as it's not correct. You don't need an #IBOutlet for anything specifically. It doesn't grant any special powers to the button, it's just a handy pointer to the object you can use.
You of course need a pointer to the object if you want to call it's methods, but the sender attribute to an #IBAction is just as good for that IBAction's code.
You do have to make sure the sender is the right type.
For example:
#IBAction func buttonPressed(sender: UIButton) {
sender.setTitle("New Title", forState: .Normal)
}
That will work fine and change the button tapped. Doesn't matter if there's an #IBOutlet pointing to it anywhere. The sender variable is all you need.
In some cases, you'll want to use an AnyObject type for sender, in which case you'll need to check the type first:
#IBAction func buttonPressed(sender: AnyObject) {
if let button = sender as? UIButton {
button.setTitle("New Title", forState: .Normal)
}
}
Now, if you wanted to change a different button's title, then an #IBOutlet can make it easier.
#IBOutlet weak var someOtherButton: UIButton!
#IBAction func buttonPressed(sender: UIButton) {
someOtherButton.setTitle("New Title", forState: .Normal)
}
But again, an #IBOutlet isn't required (it is recommended). To show #IBOutlet doesn't have any special powers, you could set a tag value (e.g. 100) on the button in Interface Builder and use code like this:
#IBAction func buttonPressed(sender: AnyObject) {
if let button = view.viewWithTag(100) as? UIButton {
button.setTitle("New Title", forState: .Normal)
}
}
The 100 number I've used here is arbitrary and could be any number so long as you only use the number for one item in the view.
#IBAction func buttonClicked(sender : AnyObject) {
println("Button was clicked", sender)
}
here sender means your button's reference. you can perform different on button on click by using sender like sender.backgroundColor etc.
Hope this will help :)
The sender argument you mention is part of the Target-Action Mechanism.
The sender parameter usually identifies the control sending the action message (although it can be another object substituted by the actual sender). The idea behind this is similar to a return address on a postcard. The target can query the sender for more information if it needs to. If the actual sending object substitutes another object as sender, you should treat that object in the same way. For example, say you have a text field and when the user enters text, the action method nameEntered: is invoked in the target:
As the sender could be substituded, there is no guarantee that someone could use another sender in the parameter if calling it manually, while if you own a reference to your button in your class, via an IBOutlet, then it could sometimes be wiser to use it for any method you expect to call as you are sure that it refers to the button.
However, if you are sure the method is only linked with the correct sender, you could potentially call any method.
The question would not as much be "what action can I call" but "am I really sure sender really is the relevent button"

How does UIButton addTarget self work?

I try figure out why self point to the GameViewController instead of Answer
GameViewController.swift
class GameViewController: UIViewController {
var gameplay = QuestionsController(colors: colors)
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(gameplay.answersController.answers[0].button)
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from GameViewController class", forState: .Normal)
}
}
QuestionsController.swift
class QuestionsController {
var question: Question
var answersController: AnswersController
}
AnswersController.swift
class AnswersController {
var answers = [Answer]()
func prepareAnswers() {
let answer = Answer()
answers.append(answer)
}
}
Answer.swift
class Answer{
let button: UIButton
func prepareButton() {
let answerButton = AnswerButton(type: .System)
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
button = answerButton
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from Answer class", forState: .Normal)
}
}
addTarget:action:forControlEvents: tells the control (answerButton in this case) what method to call, and what object to call it on, when the user taps the button. Looking at your code in more detail:
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
When the user taps a button, the TouchUpInside event fires on the answerButton, and when that happens we want to invoke a method didPressAnswerButton: on an Answer object
So, we need to tell answerButton what do do when this TouchUpEvent fires. You do this calling the addTarget:action:forControlEvents method on the answerButton
The self argument tells the answerButton what object to notify about the event: it is the target. In this context, self is an Answer object.
The "didPressAnswerButton:" argument indicates what method the answerButton should call in response to the tap event: this is the action
This is the target-action mechanism of Objective-C/Cocoa. It's a very common pattern, it's worth it to read the linked documentation to learn a bit more about how it works. The key is that this is based on Objective-C* message passing: in the code above, "didPressAnswerButton:" indicates a selector, which when paired with a target (self), tells the answerButton how to send a "message" to the target when the user taps the button.
Also, note that when you are editing a storyboard and ctrl-drag from a button to your view controller and select a method, you are also setting up a target/action using this same mechanism. You select the target object by dragging to the view controller icon (or some other icon), and then you pick the action/selector when clicking on a method name in the popup.
* Target-Action was originally designed for Objective-C, but for the common case of implementing a view controller, you can assume Swift works the same way. Just note when reading documentation that Swift uses simple strings for actions, whereas Objective-C uses #selector(...).

Is addTarget safe to use?

I'm a bit skeptical recently, with the use of addTarget() to UITableViewCell in cellForRowAtIndexPath. I'm also eager to know what is the best practice to listen button event from UITableViewCell.
My confusion begins when I see I've no way to de-register the addTarget listener that I adds to an UIButton resides in UITableViewCell:
Code for cellForRowAtIndexPath:
cell.button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
The above code registers a listener to UIButton that resides in UITableViewCell, but I see there no reference of de-registering them. I'm not sure if this process is automatic or not (to addTarget-mechanism), I haven't found any reference that said so in Apple doc (as far I've searched, at least), also.
So, my question is, is addTarget use to UITableViewCell buttons are good to use? Does they all de-registers when view controller disappears?
Or, it would be good if I use an addObserver?
override func viewDidLoad()
{
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "onCellButtonPressed:", name: "cellButtonPressed", object: nil)
}
func onCellButtonPressed(notification:NSNotification)
{
if let sender = notification.object as? UIButton
{
...
}
}
In UITableViewCell code:
#IBAction func onButtonPressed(sender: AnyObject)
{
NSNotificationCenter.defaultCenter().postNotificationName("cellButtonPressed", object: sender)
}
Any suggestion on this, would be appreciated.
The target passed to addTarget is not retained. It is safe to use; you don't need to "deregister" (or remove) the target. This is the standard mechanism for control actions (like button presses) and you should use it (versus notifications).

Resources