iPhone Navigation-Based application - some global toolbar questions - iphone

I started iPhone development two months ago, so I can't call myself "expert" ;-) But I learned a lot in these two months, a lot here from stackoverflow :)
I'm working on an iPhone app which is based on the "Navigation-Based application" app template. My RootViewController is, of course, an UITableView. From that TableView, I can navigate to some views and one TableView.
I'm using a global toolbar, it's called in the RootViewController. Buttons are added to it in the view that needs the buttons.
Now, I have some questions regarding the use of the toolbar
I insert a flexible space with that code:
UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
and a fixed space like this:
UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil];
My question: The flexible space is working, but how do I tell the fixed space which width it should have?
2. I noticed that my Todo-App "Things" uses a global toolbar with switching button sets. But in Things, there's some kind of fading effect when the button set is changing. How can I do that in my app?

The docs say:
UIBarButtonSystemItemFixedSpace
Blank space to add between other
items. Only the width property is used
when this value is set.
Set the item's width property.

To change the items on the toolbar and have it fade between the two sets, try this instance method of UIToolbar:
- (void)setItems:(NSArray *)items animated:(BOOL)animated
You basically keep the two sets of toolbar items in two arrays.

Thanks, it's working now :-)
So, question 1 is solved.
Answers to question 2 are still welcome ;-)

Related

Set UIToolbarPosition for created UIToolbar

I'm writing app which is aimed only for iOS5 devices, so I'm trying to maximize usage of new appearance API.
I can change background of my UIToolbar with following method:
[[UIToolbar appearance] setBackgroundImage:<myImage> forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
Everything works fine, but I'm looking for more customization, so I was wondering about usage of UIToolbarPosition and I run into some problems. Using InterfaceBuilder or adding UIToolbar programmatically I'm positioning it at the top of main view - so I'm expecting UIToolbarPosition to be set as UIToolbarPositionTop, but from what I'm testing it doesn't work automaticly nor can I find any API which allows me to set UIToolbarPosition (Yes, I googled it already).
So - main question - how to create UIToolbar and tag it properly, so it can response only to UIToolbarPositionTop or UIToolbarPositionBottom, so I can use:
[[UIToolbar appearance] setBackgroundImage:<myImage_1> forToolbarPosition:UIToolbarPositionTop barMetrics:UIBarMetricsDefault];
[[UIToolbar appearance] setBackgroundImage:<myImage_2> forToolbarPosition:UIToolbarPositionBottom barMetrics:UIBarMetricsDefault];
EDIT: More details, as asked.
I have multiply toolbars in my application - for instance, toolbars at top of the screen on 2 screens, toolbars acting as accessory views for keyboard and 2 toolbars at the bottom of modals screens. I'd like to maximize usage of new appearance API instead of customizing toolbars on each screens, hence I'm asking about whole UIToolbarPosition thing and how to use it.
I know I can achieve what I want just by customizing each UIToolbar separately, but I'm just curious about new API and UIToolbarPosition usage.
UIToolbarPostion isn't a property that you're supposed to be setting programmatically - instead, it allows you to tell the program how to handle a toolbar when it's in different positions. This is mostly for toolbars that are getting pushed around by screen changes (autorotation) or are on something like a navigation controller that has variable content.
That being said, if you want to directly access your toolbars so that you can use them/set properties/etc there are a couple of methods. It sounds like you know what tagging is, and this is a valid method - just give the toolbar a tag in IB or programmatically (either edit the tag property in the side bar for IB or use the .tag property when you declare the toolbar). Then you can use the viewWithTag method to access your toolbar. However, a better method would be to just create an IB property for the toolbar (same as with labels or buttons) by control-dragging over to the header file from the toolbar. Then you could just write [nameOfToolbarProperty doSomeMethod]. If you're creating your toolbar with code then just make a reference to it the same way e.g.
UIToolbar *tref = [/*toolbar creation code*/];
In conclusion, your code
[[UIToolbar appearance] setBackgroundImage:<myImage_1> forToolbarPosition:UIToolbarPositionTop barMetrics:UIBarMetricsDefault];
could be made to work by adding
//Connect this to your toolbar in Interface Builder
#property (weak, nonatomic) IBOutlet UIToolbar *tref;
to your header. Then just do
[tref setBackgroundImage:<myImage_1> forToolbarPosition:UIToolbarPositionTop barMetrics:UIBarMetricsDefault];
Note that this just tells the program what image to display if the toolbar is in the top position - it does not set the position of the toolbar. UIToolbarPosition is a constant (so you cannot set it).

Change (not init) a UIBarButtonItem identifier programmatically?

In IB I can set the identifier of a UIBarButtonItem to 'play' which adds an image of a play button (right-pointing triangle).
Is there a way to change this image programmatically? I want to change it to 'pause' when the play button is pressed.
I know you can initialize a UIBarButtonItem with an identifier but I've yet to find a way to change it after it's been initialized. Is this even possible?
The only thing I can think of is to remove the old button and initialize a new one in its place, but this hardly seems efficient.
Any thoughts?
Ok I've googled this question to death and ran into sample code from Apple where they do exactly the same thing (toggle play/pause button graphic on a toolbar button). But instead of using the built in play and pause identifiers of UIBarButtonItem they use a custom UIButton and toggle custom images.
So if Apple goes through the trouble of creating and toggling custom images on a UIButton instead of the built in play and pause UIBarButtonItem buttons then I think it's pretty safe to say there's no way to programatically change the identifier of a UIBarButtonItem.
This is what they (Apple) do to toggle the images when the button is pressed:
// Call this when the button you want to toggle is pressed:
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
Replace p.playing with whatever BOOL you want to hold the state of your button. playButton is the custom UIButton in the toolbar. pauseBtnBG and playBtnBG are the images to toggle.
This seems to work fairly well:
UIBarButtonItem *oldButton = [myToolBar.items objectAtIndex:1];
[myToolBar setItems:[NSArray arrayWithObjects:[myToolBar objectAtIndex:0], [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop target:self action:#selector(tapStopRefreshButton:)],nil] animated:NO];
[oldButton release];
In this example I had a toolbar for a UIWebView and when someone clicked Reload I wanted it to change to Stop. The toolbar only had a flexible space and the one button on it - to right-align the button - so I grabbed a reference to the old button, made a new one with the same selector as the old, reset the buttons on the tab bar, and then released the original button.
Not the prettiest, but you can use all standard buttons without having to override the button class(es).
What about 2 stacked toolbars? Then you can have some system buttons in the top one, and others in the bottom one. If the play button is pressed, then just hide the top toolbar.

UIBarButtonItem does not appear to respond to "style" property

Situation: I'm placing an instance of a system "item action" button into the right navigation button slot... no problems there. However, I want that button to display as just the icon WITHOUT a border around it (ie: "plain" style). Reading over documentation, it sounds like this should be a simple matter of just setting the UIBarButtonItem's "style" property to UIBarButtonItemStylePlain, like so:
UIBarButtonItem *shareButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:#selector(didPressShare)];
shareButton.style = UIBarButtonItemStylePlain;
self.navigationItem.rightBarButtonItem = shareButton;
[shareButton release];
However, when I implement the above code, the button appears in the nav bar with a border around it... apparently the system is not observing my UIBarButtonItemStylePlain setting. Any ideas as to why? Any other solutions for making a button appear a just the icon and no border around it?
Thanks in advance!
I'm sorry to tell you, but as far as I know you can't use plain style with UINavigationBar.
If it is possible use UIToolbar instead.

Can't set color for UIBarButtonSystemItem

I have been trying to have a UIBarButtonSystemItem in my toolbar and it always defaults to the standard blue. If I create it with an image or title it works fine with the correct color, try it with UIBarButtonSystemItem and it goes to the default color and nothing I have found on the internet works.
heres the code:
UIBarButtonItem *overlays = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPageCurl target:self action:#selector(overlays)];
Had to create my own button. Seems to be a bit of a bug that initWithBarButtonSystemItem default back to the standard colour. Few people have requested to Apple to fix it but no luck so far.
I realize this is old, but the color of the button is dependent on the color of the navigationbar. You must create a custom button like you did. This is most likely a Apple HIG deal.

How can I dynamically hide a button from a view?

I would like to dynamically hide a button in one of my views depending on a certain condition.
I tried adding some code to the view controller's -viewWillAppear method, to make the button hidden before displaying the actual view, but I still don't know how to do that.
I have a reference to the button through an IBOutlet, but I'm not sure how to move forward from here. For reference, this is a UIBarButtonItem instance.
If you're trying to hide a UIBarButtonItem, you'll actually have to modify the contents of the parent bar. If it's a UIToolBar, you'll need to set the bar's items array to an array that doesn't include your item.
NSMutableArray *items = [[myToolbar.items mutableCopy] autorelease];
[items removeObject: myButton];
myToolbar.items = items;
Set the bar item to nil.
For example:
self.navigationItem.leftBarButtonItem = nil;
So I tried Ben's winning approach but in the end I found it to be wrong for my purposes - though I'm sure it depends upon what you're trying to do. I was trying to show a nav bar button under certain conditions only and then hide it as soon as the condition was no longer met (in my case it's a "Done" button used to hide the keyboard associated with a UITextView. It should only be displayed when the user is typing in the text view). My steps were as follows:
I added a UIBarButtonItem as a
property in my UIViewController
class. I instantiate it in the
initWithNibName method.
I assigned the UIBarButtonItem property as the
rightBarButtonItem in the nav bar as
soon as the user starts typing in
the text view.
I set the UIBarButtonItem property
to nil when the user is done typing.
It's working like a charm. I'm adding some code samples below.
First to instantiate the button in my view controller init method:
barButtonItemDone = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done:)];
Then I set it as the right bar button in the delegate method that is called as soon as the user starts to edit the text view:
self.navigationItem.rightBarButtonItem=[self barButtonItemDone];
Finally, when the button itself is clicked, a method called "done" is called and I just set the rightBarButtonItem to nil inside that method:
self.navigationItem.rightBarButtonItem=nil;
If all that one is trying to hide is the back button in the navigation bar, there is an easier way:
self.navigationItem.hidesBackButton = YES;
Quote from developer documentation:
hidesBackButton
A Boolean value that determines whether the back button is hidden.
#property(nonatomic, assign) BOOL hidesBackButton
Discussion
YES if the back button is hidden when this navigation item is the top
item; otherwise, NO. The default value
is NO.
Availability
Available in iPhone OS 2.0 and later.
This is a bit of a hack, but it works in my case (and it properly handles dynamic spacing):
To hide:
myButton.width = 0.1;
To show:
myButton.width = 0.0;
A width of 0.0 is "auto width", and with a width of 0.1, the button totally disappears (not even a "sliver" of a button, though I haven't tried this on a retina display).
Another hacky solution:
myButton.customView = [[UIView alloc] init];
The best solution to this is less technical. All you need to do is create your normal navigation bar (top) or toolbar (bottom), but without the optional button. Then create another identical, but shorter bar which you then place at the part you want the optional button and create your optional button on this second shorter bar.
Now you can call hidden = YES on the whole additional bar.
The bars seamlessly overlap for me, your mileage may vary.
This answer is regarding text-based UIBarButtonItems, however, the same concept could be applied to other types of buttons as well. Note that this will allow one to both hide and show the item again. Many of the answers above (those setting the button's value to nil, for example, do not allow the button to be re-shown if that's desired).
TL;DR:
if (shouldShowMyBarButtonItem) {
self.myBarButtonItem.title = nil;
self.myBarButtonItem.action = nil;
} else if (!shouldShowMyBarButtonItem) {
self.myBarButtonItem.title = #"Title";
self.myBarButtonItem.action = #selector(mySelector:);
}
The long version:
The UIBarButtonItem I was trying to hide is in a UIToolbar, not a UINavigationBar so all the suggestions that access the left (or right) barButtonItem properties on the navigation item don't work for me. Also, as stated above, I wished to re-show the button when circumstances change.
Michael's suggestion came closest to working, however, with iOS 7 at least, there was still a very small sliver of the button displayed that was tappable. In my app, tapping the item when it's not supposed to be available was unacceptable. The above code both hides and, crucially, deactivates the button.
I call the above code in a private refresh method, which is called when a user event occurs.
This is what I did for button items that weren't part of the navigation bar (where Blank.png is a blank image I created that's the same size of the image it replaces):
theButton.enabled = NO;
theButton.image = [UIImage imageNamed: #"Blank.png"];
Ben's answer is technically correct, though when I try it on my custom UIToolbar, the items space out in a way that I don't like, because I use UIBarButtonSystemItemFlexibleSpace items.
If you want your other items to stay in the same place, you'll either have to set your flexible spaces to fixed spaces, or try what I did:
[filterBarButton.customView setHidden:YES];
note: this only works if your UIBarButtonItem uses custom views.
If you add a UIButton to the UIBarButtonItem rather than just use the UIBarButtonItem.
You can then assign UIButton.hidden to TRUE or YES and it (and the UIBarButtonItem) will not be visible or active.
Hope that helps.
Just set the button's hidden property to true:
myButton.hidden = YES;