Passing custom data in [UIButton addTarget] - iphone

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.

Related

Working with big numbers

I have a number like 12345678910111213 and I need to pass it from one method(cellForRow) to another(button action method). The simplest way which I used to use is to pass it through a button tag. In this case it is impossible(?). I can also create a property for it but what about encapsulation? I want to know really RIGHT(and preferably simple) way for doing things like that. Thanks in advance!
Well you can actually attach the value to the UIButton. When you have the value you want to pass and you have a reference to the button:
static char kMyObject;
objc_setAssociatedObject(myButton, &kMyObject, [NSNumber numberWithInt:myInt], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
On the other side, when you receive the action with the button as id:
- (void)myAction:(id)sender
{
UIButton *myButton = (UIButton*)sender;
NSNumber *number=objec_getAssociatedOject(myButton,&kMyObject);
}
You cannot pass it as a tag as Saad said. You can use NSDecimal numbers here.
#Saad cannot use double, as it will lose precision.
In this integer tag you can store a pointer (case of 32 bit address) to a class/structure/whatever what represents bigint.
For example:
UIButton *button = [UIButton ...];
button.tag = (int)[[MyBigInt alloc] initWithString:#"12131312312312312"];
after:
MyBigInt *bigInt = (MyBigInt *)button.tag;
...
[bigInt release];
I'm going to make some assumptions here, because I just when through something similar.
The UIButton with the action is in a UITableViewCell.
You have an underlying source for all your data (ie. An array with all your data in it).
You have easy access to your tableView.
First, you need to get the cell which contains the button:
UITableViewCell *cell = nil;
for (UIView *view = sender; view; view = view.superview) {
if ([view isKindOfClass:[UITableViewCell class]]) {
cell = (UITableViewCell *)view;
break;
}
}
Next, you need to get the indexRow for that cell:
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
Finally, you have to get access to your data:
ModelClass modelObject* obj = [self.data objectAtIndex:indexPath.row];
Now, you can make any changes you need to your model.

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]];
}
}

UI object -> Core Data association (Subclassing UIButton, adding instance variable)

If core data has an object that has a specific member variable value I draw a button to a view; there could be any number of these buttons on screen at the same time.
If the user clicks that button I want to get that associated Core data object.
What would be the best way allow this to happen in terms of allowing the button to reference/call the core Data object?
I have a few ideas of my own, but want to see if there is any quicker/more efficient methods.
Edit:
Button creation will always follow the creation of a managed object.
Buttons creation can also be triggered by a Core data read so Not always will the Managed object creation proceed button creation.
When I create the button, I save it to make sure it was a non-temperory UID, read its UID and store it in a varible (Subclassing UIButton). (The creation Process is optional, note the 2 bullets above). This idea is what ccjensen is getting at.
When I create the button I store 4-5 variables (Subclassing UIButton) that will allow a predicate to find the associated object in Core data.
Storing active button pointers in a dictionary with the CoreData ID
I would favour option 1, any thoughts or alternatives?
Have you considered using KVO with view controller (or whatever is responsible for creating/deleting buttons) observing the variable of interest?
For that matter, what approaches have you already considered? This might make your question more inviting to feedback from others.
Although you don't provide much details, my immediate suggestion would be to find a unique property in the core data objects that you can use for the buttons 'identifier' property. I would probably use the managed objects id or URI representation.
Then in your button handler method you can pull out the identifier of the (id)sender and that should give you the means to find the specific managed object that "belongs" to that button.
Ok, I went with Option 1...
Creation:
SomeManagedObject * managedObj = (SomeManagedObject *)[NSEntityDescription insertNewObjectForEntityForName:#"SomeManagedObject" inManagedObjectContext:myManagedContext];
NSError * er = nil;
if(![myManagedContext save:&er])NSLog(#"ERROR:SAVE Error -%#",er);
NSManagedObjectID *identifier = [managedObj objectID];
CGPoint myPoint;//set point data
if(![identifier isTemporaryID]){
CoreDataAwareButton * button = [CoreDataAwareButton buttonWithPosition:myPoint CoreDataId:identifier AndDelegate:self];
[self.documentImage button];
}
else NSLog(#"Error-save error");
On Press:
-(void)pressCoreDataAwareButton:(id)sendr
{
CoreDataAwareButton * note = (CoreDataAwareButton *)sendr;
SomeManagedObject * obj = (SomeManagedObject*)[fetchObjectFromCoreDataWithID:note.coreDataIDentifier];
}
CoreDataAwareButton.h
#import <Foundation/Foundation.h>
#interface CoreDataAwareButton : UIButton {
NSManagedObjectID * _coreDataIDentifier;
}
#property(nonatomic,retain) NSManagedObjectID * coreDataIDentifier;
+(AnnotationButton*)buttonWithPosition:(CGPoint)point CoreDataId:(NSManagedObjectID*)identifier AndDelegate:(id)del;
#end
CoreDataAwareButton.m
#import "CoreDataAwareButton.h"
#import <objc/runtime.h>
#implementation CoreDataAwareButton
#synthesize coreDataIDentifier=_coreDataIDentifier;
+(CoreDataAwareButton*)buttonWithPosition:(CGPoint)point CoreDataId:(NSManagedObjectID*)identifier AndDelegate:(id)del{
CoreDataAwareButton* button = [self buttonWithType:UIButtonTypeCustom];
if (button && (class_getInstanceSize([button class]) == class_getInstanceSize([CoreDataAwareButton class]))) {
button->isa = [CoreDataAwareButton class];//This looks dangerous, but its fine; credit: http://blog.jayway.com/2008/12/12/uibutton-troubles-and-obj-c-magic/
[button addTarget:del action:#selector(pressCoreDataAwareButton:) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(point.x, point.y, 30, 30);
//button.backgroundColor = [UIColor clearColor];
UIImage *img = [UIImage imageNamed:#"annotation_icon_large.png"];
[button setImage:img forState:UIControlStateNormal];
button.coreDataIDentifier = identifier;
}
return button;
}
-(void)dealloc{
[_coreDataIDentifier release];_coreDataIDentifier=nil;
[super dealloc];
}
#end

Selectors with arguments in Obj-C

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

UIButton inside UITableViewController

I have a UIButton that is created inside of each table cell. I want to hook up a touch event like so:
[imageButton addTarget:self
action:#selector(startVote:)
forControlEvents:UIControlEventTouchUpInside];
I want to pass data about the current row (the id of the object for that row) to the startVote method. Is there a method that I am missing to do this or am I breaking some best practice. This seems like a very normal thing to do?
I assume you have some sort of NSArray with the data that gets passed on to the buttons in cellForRowAtIndexPath.
Try this in startVote:
- (void)startVote:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSDictionary *myData = [myArray objectAtIndex:indexPath.row];
}
EDIT:
If for some reason the row is not selected, you can assign a unique tag to every button upon creation and then:
- (void)startVote:(id)sender {
int myTag = [(UIButton *)sender tag];
NSDictionary *myData = [myArray objectAtIndex:myTag];
}
Maybe you would do some sort of operation with the tag so it can be used as an index (I add a certain amount to every tag so it will not conflict with "automatic" tagging used by the OS.
The UITableViewCell doesn't know, out of the box, what row it's displaying in the table. Remember, the intent is that the same cell instances are re-used all over the table to display its data. That said, your UITableViewController is responsible for setting up the cells and passing them to the system (and has the index path, of course). You could, at that point, do something like:
Assuming it's a custom cell class, set a property on the cell instance to identify what row it's displaying, and which your button can later use.
If you're putting these buttons in the cells as their accessory views, take a look at the table delegate's tableView:accessoryButtonTappedForRowWithIndexPath: method.
If it's a one-section table, you could do something really cheesy like store the row index in the button's tag property. Your startVote: method is passed the button, and could then extract its tag.