Passing object data with UIButton press - iphone

I've created a custom UIView class FormDropdown, which contains a question & button in the nib. In the class is also an NSArray property which is supposed to store the various options for the button.
So a button can be placed by doing this, in for instance a viewDidLoad method:
FormDropdown *dropdown = [FormDropdown dropdownWithQuestion:#"This is an example question" andLabel:#"Select one" andOptions:[NSArray arrayWithObjects:#"One", #"Two", #"Three", nil]];
[self.view addSubview:dropdown];
Obviously, I'd like the button to, when tapped, bring up a UIPickerView with the options showing. But I'm stuck on how to send the options to any method. I know I can attach an action to the button like so:
[dropdown.dropdownButton addTarget:self action:#selector(dropdownPressed:) forControlEvents:UIControlEventTouchUpInside];
..but I can't see how I would pass the options from the dropdown.options array to the method?

I believe that you can do this by adding an "associative reference" from the UIButton to your object data.
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocAssociativeReferences.html

I am looking for a way to do that as well... however, it doesn't seem possible.
My possible solution: I think I am going to create a subclass of UIButton, and add a "NSObject *tagObject" property to it.
Anyone seems something wrong about it? (I am using ARC, and I am wondering if that would cause objects to remain in memory - I do not think so).

Related

How can I hide a UIBarButtonItem?

I have created a simple UI in IB, this consists of a UINavigationBar and a UIBarButtonItem that I dragged and dropped on the right hand side.
I am trying to set this button to be hidden a certain times but I am having some problems.
So far I have tried using:
self.NavigationItem.rightBarButton = nil;
...which didn't work for me. I have also tried creating and IBOutlet and linking it to the button however I'm having problems with this too. I think it should be pretty simple and maybe I'm over-complicating it, but at this point I'm pretty stumped!
Please can someone help me out?
UINavigationItem doesnt have a rightBarButton property. Try rightBarButtonItem instead (or [self.navigationItem setRightBarButtonItem:nil animated:NO];):
self.navigationController.navigationItem.rightBarButtonItem = nil;
// Or
self.navigationItem.rightBarButtonItem = nil;
// Or
[self.navigationItem setRightBarButtonItem:nil animated:NO];
Just reset the buttons
-(void)setItems:(NSArray *)items animated:(BOOL)animated
More info here: http://developer.apple.com/iphone/library/documentation/uikit/reference/UIToolbar_Class/Reference/Reference.html#//apple_ref/occ/instm/UIToolbar/setItems%3aanimated%3a
You can get the current items using the items property, then just remove the one you don't want to show and pass in the new NSArray.
You can also add a UIButton as the UIBarButtonItem's customView. Then set the hidden property on the customView (UIButton)
Rather than deleting the bar button item and destroying the button and it's attached storyboard segue, you can just set it to clear text when it's disabled.
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:#{NSForegroundColorAttributeName:[UIColor clearColor]}
forState:UIControlStateDisabled];
Then when ever you want the bar button item hidden, you can just do:
self.navigationItem.rightBarButton.enabled = NO;
It's lame there's no hidden property but this offers the same result.
Actually, you can just create an IBOutlet reference to the desired UIBarButtonItem and when needed just do as follow:
[self.yourOutletRerence setImage: nil];
The simplest solution: Just change the BarButtonItem's identifier to custom.

Overwriting UINavigationBar to set a default titleView

What I want to do is a navigation bar with a image on it. I have a tab controller on my main view, and inside each tab I have a UINavigationController. From inside the UIViewController that my tab/navigationController calls, I could set the titleView without much problem, doing this inside the viewDidLoad method:
self.navigationItem.titleView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"mylogo.png"]] autorelease];
But, I want to replace all titles in my navigationBar for this view, and it seems ugly to repeat this everywhere. So I did this on the delegate (after linking all the Outlet stuff)
self.tabOneNavController.navigationBar.topItem.titleView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"mylogo.png"]] autorelease];
Again, it worked! ok, I'm almost getting there.
But the point is, I've 5 tabs and all of them have navigationControllers inside. I reduced the code repetition from every internal view to only 5 times, but it still. It requires that I do that for the NavController of each tab.
Then I tried to extend the UINavigationBar to create my own, where I could set this in the initializer, and use it in the interface builder as the object class. But it doesn't seem to work. Here is what I did:
#implementation MyNavigationBar
- (id)init {
self = [super self];
self.tintColor = [UIColor greenColor];
self.topItem.title = #"testing please work";
return self;
}
#end
in the interface file MyNavigationBar inherits from UINavigationBar. But this didn't work. Should I overwrite other method? which one? is this a good practice?
I'm not even sure if I should add one navigationBar for each tab, as I said, I have tabs and I want to have a navigation bar / navigate inside them. By now, after a near death experience trying to figure out how the interface builder / outlets and classes work, the code is working, I just would like to make unglify it.
Thank you!
The problem of repeating code which you describe has an elegant solution. Objective-C supports something called a "category", which allows you to add methods to a class. A common use for this is to customize navigation and tab bars. In Xcode 4, you would do something like this to add a category on UINavigationBar:
Hit Command+N or open the "New File" dialog. Next, choose "Objective-C category" from the Cocoa Touch menu:
Click Next and you will be prompted to enter the name of the class that you would like to add methods to as a category. It should look something like this:
Then, you should end up with a save file dialog. A quick note about convention here. Convention is to name a category after the original class, the plus sign, and then a description of what you're adding. Here's what yours might look like:
Once you save your file, you will need get something like this:
Look at that beauty. You can now override the default drawing/init methods as well as extend the functionality of the navbar.
I'd suggest looking into the init and drawRect methods, although I don't remember which ones people use. Also, please note that while under NDA, this may change in iOS 5, so just be prepared for that possibility.
Why not define a UIViewController subclass which sets the title view via self.navigationItem.titleView and have your other view controllers extend from that class? Then you're sharing that behavior across all of your controllers without repeating the implementation.

How can I set the background image of multiple buttons in a certain view from a different view?

Basically, I cannot figure out how to change the background image. I have searched and searched and just cannot seem to find it. Anyone have any ideas? Thanks!
Update
Here is the code I use to show the View:
SettingsViewController *settingsView = [[SettingsViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:settingsView animated:YES];
If anyone needs anything else to help out I'll do my best! Thanks!
Passing a messages between different views can be done by direct call of the methods (not a good architectural solution but maybe not critical for a small projects) or with an event driven model as described in an answer above.
As for binding of a collection of objects and processing the afterward I recommend to review IBOutletCollection keyword that allows to bind multiple objects fro an InterfaceBuilder to a property with a type like NSArray.
Property declaration will look like following:
#property (nonatomic, retain) IBOutletCollection(UIButton) NSArray *buttons;
The code to change the background for all the buttons will look like following:
UIImage *backgroundImage = [UIImage imageNamed:#"background.png"];
for(UIButton *button in buttons) {
[button setBackgroundImage:backgroundImage forState:UIControlStateNormal];
}
You use setBackgroundImage:forState: to set the image.
Regarding the different views part, it depends on how you wrote your code. If the view with the buttons is controlled by the different view (by creating an instance and using addSubview:) then you can call it directly by using instanceName.buttonName (as long as you declare it as a property -thanks fichek).
If you don't manually add the view, instead through IB, you can have the button that controls the other button's image point to the IBAction in that class.
If neither of those options work you can always use NSNotificationCenter.

iOS attaching callbacks and associating different sender possible?

Suppose I have a button that I am adding to an annotation object in a mapview:
AnnotationButton* rightButton = [AnnotationButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
You will notice that the button calls the function showDetails when it is clicked.
Show details is defined as - (void)showDetails:(id)sender; and takes a sender. Is there a way to send more variables, or associate a different sender? The reason is that I want the button clicked to tell me which annotation is associated with that button. Consider the annotation to be some other object which is available during the context where the button is created.
I thought about subclassing the UIButton class, and then storing additional information within it, but that seems like a hack.
Any ideas?
If this button is being used for the rightCalloutAccessoryView or leftCalloutAccessoryView of a MKAnnotationView, your map's delegate should receive the message mapView:annotationView:calloutAccessoryControlTapped: when the button is tapped. This hands you the MKAnnotationView instance that was tapped, which has an annotation property to give you the corresponding annotation. You should make use of that instead of trying to use an action on the button directly.
No, there is no way to change what is sent to the action message. You can ask for two arguments, but they will be the button and the event that triggered it. To get what you want, you have two options (that I can think of now).
Use the button's tag property. You can give each button a unique tag which identifiies the annotation, such as the index of the annotation in an array. Then it is easy to get the annotation in your showDetails: method.
Subclass UIButton. There is nothing wrong with adding functionality to built in objects. All you need to add is a property to hold some object. Bonus: If you use a generic id type for the property and give it a generic name, such as representedObject, you can use it in other projects in the future too.
from Anomie Use objc_setAssociatedObject to add a value to the buttons without subclassing. You will probably want to add a category to UIButton to make it easier to use.

iPhone SDK 2: Programmatically adding an Info Button

I am trying to add an info button to my app to provide custom help.
Instead of adding the button to the nib and linking the event (touchUpInside) to the controller, I decided to add the button programmatically. The button shows up. When I add the target event handler to be executed when the button is touched, it does not work. That my method(doHelp) is not being called on touching the button.
When I debugged it, the event is not registered with the button! Although the code does not throw any exceptions.
Here is the code snippet FROM the view:
// Create a Button to get Help
UIButton *helpButton = [UIButton buttonWithType:UIButtonTypeInfoDark ] ;
buttonRect = helpButton.frame;
// CALCulate the bottom right corner
buttonRect.origin.x = rect.size.width - buttonRect.size.width - 8;
buttonRect.origin.y = rect.size.height - buttonRect.size.height - 8;
[helpButton setFrame:buttonRect];
[helpButton addTarget:self action:#selector(doHelp:) forControlEvents:UIControlEventTouchUpInside];
[helpButton setEnabled:TRUE];
[self addSubview:helpButton];
........
// Another METHOD ELSEWHERE in the VIEW object
-(void)doHelp:(id)Sender
{
[self setHelpNeeded:TRUE];
[self setNeedsDisplay];
}
What am I doing wrong please?
I have looked at the SDK help and samples and am really flummoxed!
Am hoping another pair of eyes will help! :-)
This code snippet is in the View Object in case you need to know.
I just added the doHelp to help the first 2 responders... thanks.
**UPDATE 6/4/09 ** -
I have been trying all night and nothing worked. I think there is something wrong in the way I have set up the method selector as my method never gets called. Everything else looks fine. Even using a NIB file does not work. I have tagged the button, retrieved it and added the method selector but to no avail. There is something fundamental which I am doing wrong... Argh!!!
Any ideas, anyone?
Resolved it finally!!! and learnt something in return. Did cost me a few days to figure this out.
The reason my UIButton object was not working was because I found that in case of a UIIMageView object:
"initWithImage: This method adjusts the frame of the receiver to match the size of the specified image. It also disables user interactions for the image view by default."
AND my UIButton had been assigned as a subview of a UIImageView control !!!
There was no errors / warnings. It just gets disabled quietly.
Solution: Created a container UIView object which now contains the UIImageView AND the button so that the button appears as overlayed on the Image but it is actually a sibling of the image and a subview of the dummy container UIView.
It's been awhile, but I think your addTarget needs to take the object that contains the doHelp: selector, like so:
[helpButton addTarget:self action:#selector(doHelp:)];
assuming somewhere in that same View you have:
- (void)doHelp: { }
passing nil to addTarget means that you're sending that selector to no recipient.
The problem is your addTarget:nil there. The selector you gave it for action is just a message it'll send to its target. You didn't give it a target, so it doesn't know what to do with that message. You probably want to pass in self instead of nil there.
I came across this while googling for a solution to the same problem. At least with the 3.x SDK, all you have to do is set the UserInteractionEnabled property of the UIImageView to YES.
Thanks for posting your discovery about the problem, I wouldn't have even thought to look at that one.
I had a similar problem where Buttons were outside of the view and did not receive tap messages
what helps is to set background colour of the parent view, to see that button is outside of it:
...
[buttonParentView addSubview: myButton];
buttonParentView.backgroundColor = [UIColor cyanColor];