For UIButton how to pass multiple arguments through #selector [duplicate] - ios

This question already has an answer here:
how to pass argument for gesture selector
(1 answer)
Closed 9 years ago.
I need to pass two actions for single UIButton.
First argument passed successfully asa follows:
[imageButton addTarget:self action:#selector(imageClicked:) forControlEvents:UIControlEventTouchUpInside];
imageButton.tag = 1;
But I need to pass another argument also for the same button:
int secondAction =10;
[imageButton addTarget:self action:#selector(imageClicked:*secondAction) forControlEvents:UIControlEventTouchUpInside];
Can anybody help how to pass two values for a single button/selector?

You can use Objective C Runtime feature for associating data with objects as :
Step 1 : Import this in your class : #import <objc/runtime.h>
step 2 : Crete a key name as : static char * kDataAssociationKey = "associated_data_key";
Step 3 : Associate data with your object (Ex: button) as :
NSString *your_data =#"Data which is being associated";
objc_setAssociatedObject(imageButton,
kDataAssociationKey,
your_data,
OBJC_ASSOCIATION_RETAIN);
Step 4 : Get associated data in your method as :
NSString *value = (NSString *)objc_getAssociatedObject(imageButton, kDataAssociationKey);
Hope it helps you.

One argument that the button can receive is (id)sender. This means you can create a new button, inheriting from UIButton, that allows you to store the other intended arguments. Hopefully these two snippets illustrate what to do.
imageButton.tag = 1;
[imageButton addTarget:self action:#selector(buttonTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
and
- (IBAction) buttonTouchUpInside:(id)sender {
MyOwnButton *button = (MyOwnButton *)sender; //MyOwnButton inherited from UIButton
or
UIButton *button = (UIButton *) sender;
//do as you please with imageButton.tag
NSLog(#"%d",button.tag);
}
please refer this link for further explanation passing-parameters-on-button-actionselector

Every event has a sender that can be get at selector method by
(void)notify:(NSNotification *)notification {
id notificationSender = [notification object];
//do stuff
}
now this sender is actually an instance whose attributes could be used to get the information about it
now what you can do is you can create a class and add some attribute to it that you want to pass through selector and then use NSNotificationCenter for broad casting your event
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notify:) name:#"Event" object:yourobjectwithattributes];
put this in the class where you want to recieve the event and have the selector
and
[[NSNotificationCenter defaultCenter] postNotificationName:#"Event" object:notificationSender];
this where you want to throw an event

Short answer: You don't. The #selector there tells the button what method to call when it gets tapped, not the arguments that it should pass to the method.
Longer answer: If you know when you're creating the button what the argument is going to be, then you can wrap it up like this:
// In loadView or viewDidLoad or wherever:
[imageButton addTarget:self action:#selector(imageButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
... later ...
- (void)imageButtonTapped:(id)sender
{
[self doStuffToMyImageWithArgument:10];
}
- (void)doStuffToMyImageWithArgument:(NSInteger)argument
{
... do what you gotta do ...
If you don't know, then you probably want to save the argument to a variable somewhere.
// In your #interface
#property (nonatomic, assign) NSInteger imageStuffArgument;
... later ...
// In loadView or viewDidLoad or wherever:
[imageButton addTarget:self action:#selector(imageButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
... later ...
- (void)respondToWhateverChangesTheArgument
{
self.imageStuffArgument = 10;
}
... later ...
- (void)imageButtonTapped:(id)sender
{
[self doStuffToMyImageWithArgument:self.imageStuffArgument];
}
- (void) doStuffToMyImageWithArgument:(NSInteger)argument
{
... do what you gotta do ...

Related

Send a value with action:#selector

my code :
UIButton *boutonSuppr = [UIButton buttonWithType:UIButtonTypeRoundedRect];
boutonSuppr.frame = rectBoutonSuppr;
[boutonSuppr setBackgroundImage: [UIImage imageNamed:#"croixTest.png"] forState:UIControlStateNormal];
[monScrollView addSubview: boutonSuppr];
int numb = 10;
[boutonSuppr addTarget:self action:#selector(boutonSupprAppuye) forControlEvents:UIControlEventTouchUpInside];
My method declared : - (void)boutonSupprAppuye:(int) numero;
My problem is that I need to send a parameter in the method because I have several UIButton. For example, I want to send "(int)numb" but when I do that :
[boutonSuppr addTarget:self action:#selector(boutonSupprAppuye:numero) forControlEvents:UIControlEventTouchUpInside];
It doesn't work.
Thank you in advance.
If you're really just using an int to identify the button that's being hit you should just set unique tags for your buttons in Interface Builder or when you create them in code, set up corresponding enums and then in your hit method, handle like so. Tags are already available in every UIView and specifically designed for that purpose.
typedef NS_ENUM(NSInteger, ButtonTag) {
e_buttonTag_unknown = 0,
e_buttonTag_doThing1Button, /* 1 */
e_buttonTag_doThing2Button /* 2 */
};
...
// Add this to the button creation code you posted
boutonSuppr.tag = e_buttonTag_doThing1Button;
// and set the button's action to #selector(buttonTapped:).
...
- (IBAction) buttonTapped: (id) sender
{
UIButton *whichButton = (UIButton *) sender;
ButtonTag whichTag = whichButton.tag;
switch (whichTag)
{
case e_buttonTag_doThing1Button:
// "Do thing 1" button was pressed.
break;
case e_buttonTag_doThing2Button:
// "Do thing 2" button was pressed.
break;
default:
// Some other button was pressed.
break;
}
}
When you declare a #selector(), it can be considered the same as a function pointer. It does not take parameters at that time; it takes parameters when the selector is called.
This particular selector will be called by the UIButton class when the UIControlEventTouchUpInside event is received. This means that the execution will be out of reach of your source code and any parameters passed into it will be done so automatically by the system.
If you want to attach data to a UIButton, the simplest way to do this is with a #property.
#interface MyButton : UIButton
#property (assign) int numero;
#end
#implementation MyButton
#end
Then your construction becomes:
MyButton *boutonSuppr = [MyButton buttonWithType:UIButtonTypeRoundedRect];
boutonSuppr.numero = numero;
// etc.
Action methods should take an object pointer as argument.
Normally this is type id but sometimes a more specific object.
The key is it is a pointer and that means you can stuff anything in there in C if you pass in a pointer.
For ease just use an object and wrap your value in the object.
You could use NSValue for any C type wrapper or NSNumber for scalars

Xcode: Send value through target action from for loop

I've got a list of values coming from a database, which each has it's own unique id. I want to be able to delete a row from the list using that id. My issue is, I'm trying to understand how to send a value through the button action to be used in the called function.
For ex:
NSString *sId = [_idArray objectAtIndex:i];
_fId = [sId intValue];
[deleteBtn addTarget:self action:#selector(deleteFeed:_fId) forControlEvents:UIControlEventTouchUpInside];
The _fId is the value I'm trying to understand how to send to the function deleteFeed. I know it must be something simple, but I just can't pin it down when searching Google.
addTarget has defined set of params and you cannot send custom.
As a workaround you can do below:
Use the following api and set the _fId as TAG to the button:
action:#selector(deleteFeed:)
i.e.
[deleteBtn setTag:_fId]
[deleteBtn addTarget:self action:#selector(deleteFeed:) forControlEvents:UIControlEventTouchUpInside];
Now retrieve the tag from the button from the associated tag to identify the button.
- (void) deleteFeed:(UIButton*)sender{
[self deleteWithTag:sender.tag];
// Or place opening logic right here
}
I hope this helps.
What about subclassing UIButton? This way you'll be able to name the property something appropriate instead of reusing the ambiguous tag.
#interface ButtonWithData : UIButton
#property (assign) int aValue;
#end
- (void)yourForLoopFunction {
for (NSString *sId in _idArray) {
NSString *sId = [_idArray objectAtIndex:i];
ButtonWithData *deleteBtn = [ButtonWithData buttonWithType:UIButtonTypeCustom];
deleteBtn.aValue = [sId intValue];
[deleteBtn addTarget:self action:#selector(deleteFeed:) forControlEvents:UIControlEventTouchUpInside];
[someView addSubview:deleteBtn];
}
}
This will only work if you create a new button for each array item that you want to delete. Without more code context, I can't create a better example for you to follow, unfortunately.

how to pass data when button is clicked

Lets say I have 10 buttons. For each button i want to pass some text... e.g. Button 1, My New Button 2, etc....
What I want to do is print this text in NSLog.
Hence I created one method and passed this to button. But I am not getting how can I pass data into it...
[myButton addTarget:self action:#selector(btnSelected:) forControlEvents:UIControlEventTouchUpInside];
-(IBAction)btnSelected:(id)sender {
NSLog(#"btnSelected data is %#", sender);
// I want to print some text for respective button here...
}
But I am not getting... any idea how to get this done?
You can associate data with objects as :
Firstly import this class : #import <objc/runtime.h>
Then create a key as
static char * kIndexPathAssociationKeySTR = "associated_string_key";
then associate string as :
** Here you can associate any type of data with button like : NSMutableArray or NSString etcetra**
NSString *myAttachedValue = #"This is the info I am associating with button";
objc_setAssociatedObject(self.testBtn,
kIndexPathAssociationKeySTR,
myAttachedValue,
OBJC_ASSOCIATION_RETAIN);
then access it in your method you called on button event as :
- (IBAction)btnTouched:(UIButton *)sender {
NSString *valueIs = (NSString *)objc_getAssociatedObject(self.testBtn, kIndexPathAssociationKeySTR);
NSLog(#"value is : %#",valueIs);
}
Hope it helps you.
set tag for the button
[myButton addTarget:self action:#selector(btnSelected:) forControlEvents:UIControlEventTouchUpInside];
myButton.tag = 1;
now compare tag
-(IBAction)btnSelected:(id)sender {
UIButton *button = (UIButton *)
if(button.tag == 1) { //do button 1 stuff
NSLog(#"btnSelected data is %#", sender);
}
}
Also you can probably checkout Blocks, extend a uibutton to support blocks (UIButton block equivalent to addTarget:action:forControlEvents: method?).
Try this,
[myButton setAccessibilityValue:#"Some text"];
[myButton addTarget:self action:#selector(btnSelected:) forControlEvents:UIControlEventTouchUpInside];
-(IBAction)btnSelected:(id)sender {
NSLog(#"btnSelected data is %#", [sender accessibilityValue];);
// I want to print some text for respective button here...
}

Passing parameters to addTarget:action:forControlEvents

I am using addTarget:action:forControlEvents like this:
[newsButton addTarget:self
action:#selector(switchToNewsDetails)
forControlEvents:UIControlEventTouchUpInside];
and I would like to pass parameters to my selector "switchToNewsDetails".
The only thing I succeed in doing is to pass the (id)sender by writing:
action:#selector(switchToNewsDetails:)
But I am trying to pass variables like integer values. Writing it this way doesn't work :
int i = 0;
[newsButton addTarget:self
action:#selector(switchToNewsDetails:i)
forControlEvents:UIControlEventTouchUpInside];
Writing it this way does not work either:
int i = 0;
[newsButton addTarget:self
action:#selector(switchToNewsDetails:i:)
forControlEvents:UIControlEventTouchUpInside];
Any help would be appreciated :)
action:#selector(switchToNewsDetails:)
You do not pass parameters to switchToNewsDetails: method here. You just create a selector to make button able to call it when certain action occurs (touch up in your case). Controls can use 3 types of selectors to respond to actions, all of them have predefined meaning of their parameters:
with no parameters
action:#selector(switchToNewsDetails)
with 1 parameter indicating the control that sends the message
action:#selector(switchToNewsDetails:)
With 2 parameters indicating the control that sends the message and the event that triggered the message:
action:#selector(switchToNewsDetails:event:)
It is not clear what exactly you try to do, but considering you want to assign a specific details index to each button you can do the following:
set a tag property to each button equal to required index
in switchToNewsDetails: method you can obtain that index and open appropriate deatails:
- (void)switchToNewsDetails:(UIButton*)sender{
[self openDetails:sender.tag];
// Or place opening logic right here
}
To pass custom params along with the button click you just need to SUBCLASS UIButton.
(ASR is on, so there's no releases in the code.)
This is myButton.h
#import <UIKit/UIKit.h>
#interface myButton : UIButton {
id userData;
}
#property (nonatomic, readwrite, retain) id userData;
#end
This is myButton.m
#import "myButton.h"
#implementation myButton
#synthesize userData;
#end
Usage:
myButton *bt = [myButton buttonWithType:UIButtonTypeCustom];
[bt setFrame:CGRectMake(0,0, 100, 100)];
[bt setExclusiveTouch:NO];
[bt setUserData:**(insert user data here)**];
[bt addTarget:self action:#selector(touchUpHandler:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:bt];
Recieving function:
- (void) touchUpHandler:(myButton *)sender {
id userData = sender.userData;
}
If you need me to be more specific on any part of the above code — feel free to ask about it in comments.
Need more than just an (int) via .tag? Use KVC!
You can pass any data you want through the button object itself (by accessing CALayers keyValue dict).
Set your target like this (with the ":")
[myButton addTarget:self action:#selector(buttonTap:) forControlEvents:UIControlEventTouchUpInside];
Add your data(s) to the button itself (well the .layer of the button that is) like this:
NSString *dataIWantToPass = #"this is my data";//can be anything, doesn't have to be NSString
[myButton.layer setValue:dataIWantToPass forKey:#"anyKey"];//you can set as many of these as you'd like too!
*Note: The key shouldn't be a default key of a CALayer property, consider adding a unique prefix to all of your keys to avoid any issues arising from key collision.
Then when the button is tapped you can check it like this:
-(void)buttonTap:(UIButton*)sender{
NSString *dataThatWasPassed = (NSString *)[sender.layer valueForKey:#"anyKey"];
NSLog(#"My passed-thru data was: %#", dataThatWasPassed);
}
Target-Action allows three different forms of action selector:
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
I made a solution based in part by the information above. I just set the titlelabel.text to the string I want to pass, and set the titlelabel.hidden = YES
Like this :
UIButton *imageclick = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
imageclick.frame = photoframe;
imageclick.titleLabel.text = [NSString stringWithFormat:#"%#.%#", ti.mediaImage, ti.mediaExtension];
imageclick.titleLabel.hidden = YES;
This way, there is no need for a inheritance or category and there is no memory leak
I was creating several buttons for each phone number in an array so each button needed a different phone number to call. I used the setTag function as I was creating several buttons within a for loop:
for (NSInteger i = 0; i < _phoneNumbers.count; i++) {
UIButton *phoneButton = [[UIButton alloc] initWithFrame:someFrame];
[phoneButton setTitle:_phoneNumbers[i] forState:UIControlStateNormal];
[phoneButton setTag:i];
[phoneButton addTarget:self
action:#selector(call:)
forControlEvents:UIControlEventTouchUpInside];
}
Then in my call: method I used the same for loop and an if statement to pick the correct phone number:
- (void)call:(UIButton *)sender
{
for (NSInteger i = 0; i < _phoneNumbers.count; i++) {
if (sender.tag == i) {
NSString *callString = [NSString stringWithFormat:#"telprompt://%#", _phoneNumbers[i]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:callString]];
}
}
}
As there are many ways mentioned here for the solution, Except category feature .
Use the category feature to extend defined(built-in) element into your
customisable element.
For instance(ex) :
#interface UIButton (myData)
#property (strong, nonatomic) id btnData;
#end
in the your view Controller.m
#import "UIButton+myAppLists.h"
UIButton *myButton = // btn intialisation....
[myButton set btnData:#"my own Data"];
[myButton addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
Event handler:
-(void)buttonClicked : (UIButton*)sender{
NSLog(#"my Data %#", sender. btnData);
}
You can replace target-action with a closure (block in Objective-C) by adding a helper closure wrapper (ClosureSleeve) and adding it as an associated object to the control so it gets retained. That way you can pass any parameters.
Swift 3
class ClosureSleeve {
let closure: () -> ()
init(attachTo: AnyObject, closure: #escaping () -> ()) {
self.closure = closure
objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
}
#objc func invoke() {
closure()
}
}
extension UIControl {
func addAction(for controlEvents: UIControlEvents, action: #escaping () -> ()) {
let sleeve = ClosureSleeve(attachTo: self, closure: action)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
}
}
Usage:
button.addAction(for: .touchUpInside) {
self.switchToNewsDetails(parameter: i)
}
There is another one way, in which you can get indexPath of the cell where your button was pressed:
using usual action selector like:
UIButton *btn = ....;
[btn addTarget:self action:#selector(yourFunction:) forControlEvents:UIControlEventTouchUpInside];
and then in in yourFunction:
- (void) yourFunction:(id)sender {
UIButton *button = sender;
CGPoint center = button.center;
CGPoint rootViewPoint = [button.superview convertPoint:center toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rootViewPoint];
//the rest of your code goes here
..
}
since you get an indexPath it becames much simplier.
See my comment above, and I believe you have to use NSInvocation when there is more than one parameter
more information on NSInvocation here
http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html
This fixed my problem but it crashed unless I changed
action:#selector(switchToNewsDetails:event:)
to
action:#selector(switchToNewsDetails: forEvent:)
I subclassed UIButton in CustomButton and I add a property where I store my data. So I call method: (CustomButton*) sender and in the method I only read my data sender.myproperty.
Example CustomButton:
#interface CustomButton : UIButton
#property(nonatomic, retain) NSString *textShare;
#end
Method action:
+ (void) share: (CustomButton*) sender
{
NSString *text = sender.textShare;
//your work…
}
Assign action
CustomButton *btn = [[CustomButton alloc] initWithFrame: CGRectMake(margin, margin, 60, 60)];
// other setup…
btnWa.textShare = #"my text";
[btn addTarget: self action: #selector(shareWhatsapp:) forControlEvents: UIControlEventTouchUpInside];
If you just want to change the text for the leftBarButtonItem shown by the navigation controller together with the new view, you may change the title of the current view just before calling pushViewController to the wanted text and restore it in the viewHasDisappered callback for future showings of the current view.
This approach keeps the functionality (popViewController) and the appearance of the shown arrow intact.
It works for us at least with iOS 12, built with Xcode 10.1 ...

How to pass a variable to a UIButton action

I want to pass a variable to a UIButton action, for example
NSString *string=#"one";
[downbutton addTarget:self action:#selector(action1:string)
forControlEvents:UIControlEventTouchUpInside];
and my action function is like:
-(void) action1:(NSString *)string{
}
However, it returns a syntax error.
How to pass a variable to a UIButton action?
Change it to read:
[downbutton addTarget:self action:#selector(action1:) forControlEvents:UIControlEventTouchUpInside];
I don't know about the Iphone SDK, but the target of a button action probably receives an id (usually named sender).
- (void) buttonPress:(id)sender;
Within the method call, sender should be the button in your case, allowing you to read properties such as it's name, tag, etc.
If you need to distinguish between multiple buttons, then you could mark your buttons with tags like this:
[downbutton addTarget:self action:#selector(buttonPress:) forControlEvents:UIControlEventTouchUpInside];
downButton.tag = 15;
In your action delegate method you can then handle each button according to its previously set tag:
(void) buttonPress:(id)sender {
NSInteger tid = ((UIControl *) sender).tag;
if (tid == 15) {
// deal with downButton event here ..
}
//...
}
UPDATE: sender.tag should be a NSInteger instead of a NSInteger *
You can use associative references to add arbitrary data to your UIButton:
static char myDataKey;
...
UIButton *myButton = ...
NSString *myData = #"This could be any object type";
objc_setAssociatedObject (myButton, &myDataKey, myData,
OBJC_ASSOCIATION_RETAIN);
For the policy field (OBJC_ASSOCIATION_RETAIN) specify the appropriate policy for your case.
On the action delegate method:
(void) buttonPress:(id)sender {
NSString *myData =
(NSString *)objc_getAssociatedObject(sender, &myDataKey);
...
}
Another option for passing variables, which I find to be more direct than the tag from leviatan's answer is to pass a string in the accessibilityHint. For example:
button.accessibilityHint = [user objectId];
Then in the action method of the button:
-(void) someAction:(id) sender {
UIButton *temp = (UIButton*) sender;
NSString *variable = temp.accessibilityHint;
// anything you want to do with this variable
}
The only way I've found to do this is set an instance variable before calling the action
You can extends UIButton and add a custom property
//UIButtonDictionary.h
#import <UIKit/UIKit.h>
#interface UIButtonDictionary : UIButton
#property(nonatomic, strong) NSMutableDictionary* attributes;
#end
//UIButtonDictionary.m
#import "UIButtonDictionary.h"
#implementation UIButtonDictionary
#synthesize attributes;
#end
You can set tag of the button and access it from sender in action
[btnHome addTarget:self action:#selector(btnMenuClicked:) forControlEvents:UIControlEventTouchUpInside];
btnHome.userInteractionEnabled = YES;
btnHome.tag = 123;
In the called function
-(void)btnMenuClicked:(id)sender
{
[sender tag];
if ([sender tag] == 123) {
// Do Anything
}
}
You can use the strings of the UIControlStates that you dot'n use:
NSString *string=#"one";
[downbutton setTitle:string forState:UIControlStateApplication];
[downbutton addTarget:self action:#selector(action1:) forControlEvents:UIControlEventTouchUpInside];
and the action function:
-(void)action1:(UIButton*)sender{
NSLog(#"My string: %#",[sender titleForState:UIControlStateApplication]);
}

Resources