Selectors with arguments in Obj-C - iphone

So basically I have a big list of buttons that's present dropdowns and other things, and these buttons are created dynamically. So to capture the value for the appropriate button's data, I need to set it's action selector to a function that takes 1 extra parameter.
For example, using this selector on this dropdown, with the method below, returns an error that the selector is unrecognized. How can I get the selector to recognize the parameter I'm passing in? (In this case the variable 'name')
The apple docs at:
http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocSelectors.html#//apple_ref/doc/uid/TP30001163-CH23-SW1
On the last paragraph in the header 'The Target-Action Design Pattern', the Apple Docs imply that this can be done, but do not give an example of using a custom message, or maybe I'm just misunderstanding?
SEL sel = #selector(openDropdown:name:);
[dropdownSelector addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
-(void) openDropdown: (NSString *) anotherArg : (id) sender {
// Stuff here based on anotherArg
}

You should be able to derive the clicked button's information from the id input arg
UIButton *button = (UIButton *) sender
NSString *title = [button currentTitle];
No need to pass the extra param

What you're asking can't be done. From the docs:
UIKit allows three different forms of action selector:
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
Since you have no influence on the event parameter, the sender object must include all information you want to pass to the action method.
Despite what you have written in your comment on mihirsm's answer, you can indeed subclass UIButton to add all the additional info you want to each button instance. You could also use the button's tag property to identify it (assign a unique tag to each button) and store all the additional info in an array or dictionary using the tags as keys.
Update: In the future, you can also use associative storage to add data to objects without subclassing them but this technology is not (yet) available on the iPhone platform (10.6 only at the moment).

CALayers support arbitrary keys for key-value coding; you can use this to attach arbitrary layers:
[[button1 layer] setValue:#"firstButtonData" forKey:#"myKey"];
[[button2 layer] setValue:#"secondButtonData" forKey:#"myKey"];
And later:
- (void)action:(id)sender forEvent:(UIEvent *)event
{
NSLog(#"Data for the button that was pressed: %#", [[sender layer] valueForKey:#"myKey"]);
}
Be careful not to collide with any of the existing properties on CALayer

Related

UIControl: sendActionsForControlEvents omits UIEvent

I want to implement a custom subclass of UIControl. It works beautifully except for one fatal problem that is making me spit teeth. Whenever I use sendActionsForControlEvents: to send an action message out, it omits to include a UIEvent. For example, if I link it to a method with the following signature:
- (IBAction) controlTouched:(id)sender withEvent:(UIEvent *)event
... the event always comes back as nil! The problem seems to occur within sendActionsForControlEvents:
Now, I need my IBAction to be able to determine the location of the touch. I usually do so by extracting the touches from the event. Surely there must be some way to ensure that the correct event is delivered? It's a pretty fundamental part of using a UIControl!
Anyone know a legal workaround?
I would assume that this is because the sendActionsForControlEvents: method can't know which UIEvent (if any) your control event should be associated with.
You could try to send all the actions separately (replicating what the sendActionsForControlEvents: method does, according to the documentation), so you can specifically associate them with a UIEvent:
UIEvent *event = ...;
UIControlEvents controlEvent = ...;
for (id target in [self allTargets]) {
NSArray *actions = [self actionsForTarget:target forControlEvent:controlEvent];
for (NSString *action in actions) {
[self sendAction:NSSelectorFromString(action) to:target forEvent:event];
}
}
I have ONE possible solution at the moment, but I'm not very happy about it. For others faced with the same problem though, here it is. First, declare a local variable or property for a UIEvent thus:
#property (nonatomic, assign) UIEvent * currentEvent;
Now, in your touch-handling routines, set that local variable to the current UIEvent for that routine before calling [self sendActionsForControlEvents:] like so, replacing UIControlEventTouchDown with whichever action you want to send out of course.
self.currentEvent = event;
[self sendActionsForControlEvents: UIControlEventTouchDown];
Finally, override the following method thus:
- (void) sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
[super sendAction:action to:target forEvent:self.currentEvent];
}
This works, but I am not in the least bit fond of it, so if anybody has an alternative solution that doesn't rely on holding a weak reference to a UIEvent, I will be overjoyed to hear it!

Passing any control as an argument of function

I want to make a common function which takes a control as an argument (like UITextField, UIButton etc.)
Its working fine if I do like this
- (void) myFunction : (UITextField*) : control
{
}
//But I want to make it common for any control
- (void) myFunction : (`I don't know what to write here`) : control
{
//suppose if control is UITextField, I can set its font and its size.
//something like this
[control setFont:[UIFont fontWithName:#"Verdana" size:12]];
}
Is this possible?
You can also go like this
- (void) myFunction : (id) control
{
if([control isKindOfClass:[UITextField class]]){
// Your textfield condition
}
}
You should pass UIControl, since it's the super class for the controls.
Then in your code, you should use methods like respondsToSelector: to determine whether or not the control passed in can do what you need it to do.
You could check its class type using isKindOfClass: or isMemberOfClass as well.
Once you know which object you're dealing with, you could type cast it to save on some typing and remove any warnings about not responding to selectors, like this:
// decided that it's a UITextField after using `respondsToSelector:` or `isKindOfClass:`
UITextField *aTextField = (UITextField *)control;
This method is known as "duck-typing" - since it's similar to saying "If it walks like a duck and sounds like a duck, it'll probably be a duck".
UIControl is the superclass of UITextField, UIButton, and other controls, so this is what you want:
- (void) myFunction:(UIControl *) control
Yes, It is possible. You can pass UIView as an argument like Below
- (void) myFunction:(UIView*)customView{
if([customView isKindOfClass:[UIImageView class]]){
// This is UIImageview
}
}
Hope this Help.
use generic ID then cast your control and ask if it's a UITextField (or subclass of...)
- (void) myFunction : (id) control
{
//suppose if control is UITextField, I can set its font and its size.
//something like this
(UITextField*)aText = (UITextField*)control;
if ([aText.class isSubclassOfClass:[UITextField class]]) {
[aText setFont:[UIFont fontWithName:#"Verdana" size:12]];
}
}

Passing custom data in [UIButton addTarget]

How do I add custom data while specifying a target in a UIButton?
id data = getSomeData();
[button addTarget:self
action:#selector(buyButtonTapped:event:)
forControlEvents:UIControlEventTouchUpInside];
I want the buyButtonTapped function to look like:
(void) buyButtonTapped: (UIButton *) button event: (id) event data: (id) data
not possible. the action that is triggered by an UIControl can have 3 different signatures.
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
None of them allows you to send custom data.
Depending on what you want to do you can access the data in different ways.
For example if the button is in a UITableView you could use something like this:
- (IBAction)buttonPressed:(UIButton *)sender {
CGPoint buttonOriginInTableView = [sender convertPoint:CGPointZero toView:tableView];
NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:buttonOriginInTableView];
NSData *customData = [myDataSource customDataForIndexPath:indexPath];
// do something
}
There is always a way to get the data without passing it to the button.
You cannot send extra data to the action method. There are a number of ways to associate the data with the button, although none are particularly straightforward unless the data is a single NSInteger.
You can use the tag property to hold a single NSInteger. This may be all you need, or you could use it to look up an object in an array or dictionary.
You can subclass UIButton to add ivars/properties to store your needed data.
You can use [NSValue valueWithNonretainedObject:button] as a key for a dictionary.
My personal favorite for one-offs, you can use [associative references] to associate the data object with the button.
You can't really do that. What you can do is put the data in a dictionary and use the button to get it later.
E.g.
myDataDict = [NSDictionary dictionaryWithObjectsAndKeys:someData, button, nil];
Then later;
-(void) buttonPress:(id)sender
{
data = [dataDict objectForKey:sender];
}
If your buttons are specified in InterfaceBuilder you can use the 'tag' property of a button to lookup the data, although you will need to convert it to an NSNumber for use with the dictionary.

#selector key word in iPhone programming

-(void)displayNameBy:(NSString*)name{
mylable.text = name;
}
i want call this method using #selector keywords.
eg:
[MyButton addTarget:self action:***#selector(displayNameBy:name)*** forControlEvents:UIControlEventTouchUpInside];
Here bold italics is my doubt.. could i pass name parameter from here. when i try to pass name value i getting error.
any way to get name value in the displayNameBy:name method . using #selector key words.
here MyButton i created by programatically . not in interface builder.
thanks and regards
Have an intermediate method.
like:
{
...
[MyButton addTarget:self action:#selector(displayName) forControlEvents:UIControlEventTouchUpInside];
...
}
-(void) displayName
{
[self performSelector:#selector(displayNameBy:) withObject:#"name"];
}
-(void)displayNameBy:(NSString*)name{
mylable.text = name;
}
If you create a one-argument selector for your action (like you're doing in displayNameBy:), the argument is the sender of the action. In this case, your button instance.
According to the UIControl docs, there are three different types of supported selectors:
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
Where are you expecting name to be defined? Is it based on the user's actions, the app state, or something else? Depending on what method is used, you could create a UIButton subclass that includes either the necessary logic or declares a delegate protocol, set the delegate to your view controller, and have the delegate implementation set the button's label. The latter is probably better from a MVC standpoint, unless the source of name is blindingly simply (ie, an attribute that could be set on the button, not something the button would have to calculate based on other application state).
The method's selector is just displayNameBy:. The name it the end is the name of the parameter. However, I don't know where you're expecting this NSString *name parameter to come from. The argument for an action method is the sender, which will be the button in this case. So it would be - (void)displayNameBy:(id)sender.
If you're trying to pass the parameter through the selector, that isn't possible. A selector is literally just a name — it doesn't specify any particular behavior.
If you wanted to use PLBlocks, you could create a trampoline class that would be called like:
[myButton setTarget:[BlockProxy proxyWithBlock:^{ [self displayNameBy:name]; }] action:#selector(call:) forControlEvents:UIControlEventTouchUpInside];
That's the closest you'll get, I think. Because what you really want is for the button to call a closure, which is what PLBlocks gives you. Whether it's worth the trouble to get this kind of expressiveness is you're call.

How to add (id) sender to the following -(IBAction)?

How do you add a (id) sender to the following code?
- (IBAction) gobutton: (UIButton *) button5 {
Everything I try fails, any help would be appreciated. Thanks.
EDIT: I need to keep the (UIButton *) button 5 reference in the (IBAction)
If I recall correctly, and if you are using this in the way I think you are,
- (IBAction) gobutton: (id) sender {
if(sender == button5)
//do something...
else
//do something else...
}
Assuming that you specified button5 as a parameter to indicate that this executes in response to button5 being pressed.
Ok, first.... IBAction doesn't really mean anything special except to Interface Builder. Basically:
#define IBAction void
So whenever you see IBAction, think "void". The only reason it's there is as a flag to tell Interface Builder that a method is a valid method to connect control actions to. The Objective-C compiler doesn't need to know about it and so it's defined to void since all "action" methods return void.
Second, action methods also have one argument which could be an object of any number of types. Because of this, action methods are supposed to use type id as the type for their argument. That way they can be passed a pointer to any Objective-C object without causing the compiler to generate a type checking error.
So usually actions should work something like this:
- (IBAction)myAction:(id)sender {
if (sender == self.someButton) {
UIButton *button = (UIButton *)sender;
...
return;
} else if (sender == self.someControl) {
UIControl *control = (UIControl *)sender;
...
return;
}
}
In other words, an id is almost like an untyped pointer like a void * is routinely used in C when some function needs to take a pointer to something of unknown type. sender could be different types of control, so something generic like id is used then sender is cast to something more specific once the code knows what it is.
Anyway, there is absolutely no reason to define something as having a return type of IBAction unless you are going to use that method as a target action in Interface Builder. Having an IBAction in your app delegate seems kind of unusual....
It's not clear what you are trying to do but most actions look like:
- (IBAction) gobutton: (id)sender;
The first parameter to an action is always the sender (you can specify the type and name as appropriate).
If a method is the action for a button, then the first parameter will be the button. If that method is the action for several buttons, then the first parameter will allow you to determine which button was tapped (as Leper describes).
What problem are you actually trying to solve?
There are techniques for passing information to the action method. For example, if you have a button that appears on a table view cell and performs the same action for every cell, then in the action method, you would want to be able to determine which cell's button was tapped.
How can I get the id of the sender before the user touches the control?
Found it! Set a tag and the use viewWithTag.
Can you create a simple structure that contains both the UIButton and the sender and use that?
struct myObject
{
UIButton* button5;
id sender;
}
...or, you could create your own NSObject (probably more cocoa-y):
#instance myObject : NSObject
{
...
}