Crappy iOS APIs – UINavigationController

While Apple has some great products and has achieved excellence designing beautiful UIs, the same can’t be said for many of its APIs. Anyone who has tried to do even the simplest of the things just by looking at the documentation or based on assumptions of how one could expect that a given thing would work based on context and method names, would know what I am talking about: literally hours trying to correctly setup a more complex UILabel, or pulling the hair out of the head after not being able to easily change the color of an UINavigationController or UIToolBar. The list goes on ad-infinitum.

Take UINavigationController for instance: it has methods to set the title, the left and right buttons, and for the back button when a new controller is pushed into the stack. So imagine you are in controller A,which pushes controller into the stack by calling [self.navigationController pushViewController:b animated:YES]. The view slides left with a nice looking animation, and b’s view pops in the screen. By default, iOS will automatically set up a working back button for you – which is great – and that button’s label is set to the previous screen’s title – that, again, makes a lot of sense.

However, if for any reason you’d like to change the label of the back button to another text. you can’t just do

self.navigationItem.backBarButtonItem.title = @"Back";

on b. Well, you can, but it won’t work. It does not crash the app, but also does nothing. Now imagine for a moment that doing the previous code is the most natural thing anyone could possible do, but it does not work. “Ok then, maybe if I override the entire button”:

self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Back"
	style:UIBarButtonItemStyleBordered target:nil action:nil] autorelease];

only that it also does not work: you’d still get the default button, despite assigning a new one. Lots of tries here and there, and you finally figure out (by looking a Google or deciphering the poorly written documentation) that the only way to get it working is to set the title BEFORE you push b into the stack, in the PREVIOUS controller. In other words, you must the the title you’d like to see in b in the a controller. Genius, right?! Apple just killed SoC. It will look like this:

// Somewhere in AController
BController *b = [[BController alloc] init];
self.title = @"Back"; // Scream in pain
self.navigationItem.back
[self.navigationController pushViewController:b animated:YES];
[b release];

There simply can’t be any good reason on earth to do that. It get’s worse then you need to have a custom method called before the view pops out of the stack, as it is not possible to override backButton‘s selector.

So that one should do, instead? Well, what I do is the following: I force backButton to be hidden when the new controller slides up on the screen, and then set up a “left” button:

// BController
-(void) viewDidLoad {
	self.navigationItem.hidesBackButton = YES; // Important
	self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Back"
		style:UIBarButtonItemStyleBordered target:self action:@selector(myCustomBack)] autorelease];
}

-(void) myCustomBack {
	// Some anything you need to do before leaving
	[self.navigationController popViewControllerAnimated:YES];
}

it just works. It’s a lot of work, of course, but works. Now, if you’d like to have a different layout for such button, that’s a whole new story.

10 thoughts on “Crappy iOS APIs – UINavigationController

  1. I can not agree more!!!

    I’ve gone crazy trying to figure this all out and it really is close to insanity… you would think that the simple things would be simple to implement and with all the glory of Xcode and IB… but NNNNOOOOOOOOOO… something (that is usually not documented) has to come out and bite you in the ass every once in a while.

    My latest issue with Xcode is that my app crashes when pushing a view controller with “animated” set to “YES”, but it works fine when “animated” is set to “NO”.

    *literally going to bang my head against a wall*

  2. Hey Johnny,

    I have had some problems with this kind of animation as well. In my case, I was trying to execute a code after the call to animated:YES, but (I don’t know why) the OS just ignored any further code.
    My deduction is that the animation block it uses internally interferes somehow in the rest of the code that might run while the animation is in place.. I don’t remember exactly what my situation was, but you may want to look for some initialization code in viewWillAppear or in the code just after animated:YES.

    If you want (and can), add the piece of code here.

  3. Hey,

    You can actually use self.navigationItem.backBarButtonItem to override the back button but you have to do it in the viewDidLoad method of the previous controller, not the controller that is getting pushed.

    Not the most natural thing but really works.

  4. @maxime, yes, that way it works, but as you said, it’s not natural and fits on my complains about the API. Also, setting it in the previous controller sometimes mixes too much logic from two different classes (like, for example, when the logic for the back requires some more work).

  5. Thank God that I found this!!! Been tearing my hair out as well. Totally agree with you about the crass stupidity of some of these iOS API’s.

    What arcane reason do I have for changing the title of the Back button, I hear you cry? Simple: I’m writing a Welsh-English app where the typical user wants to click a segmented control to jump between the two languages. For this reason, I want to display “Back” in Welsh or English after I have already pushed my settings view controller on the stack. Is this rocket science? Hardly.

    Thank you Rafael for providing the solution. I’ve looked at many solutions, and yours is the *only* one that works.

    Dave

  6. Actually, this way makes perfect sense. The reason why it does this is, I think, is because you might have conditional logic to control which VC is pushed onto your navbar.

    If you are just pushing VCs in a straight line onto the navbar, then the one you are pushing will always know which one it is popping to and hence what title should be in the button.

    If the VC you pushed changes based on a condition, then you would have to put conditional logic in the pushed VC to determine what the title in the button should be.

    So A->B->C is no problem

    A->(B or C)->D, D would have to know something about whether B or C pushed it. You could argue then, “OK, I can just do that when I set up the VC in B or C”, but the way Apple has it, you don’t have to do anything, D just knows without adding an if statement in the method that pushes the controller.

    I think??

    • That is exactly why, and it makes perfect sense in most cases :)

      But in some cases it’s really annoying that you cannot override that back button text, from the view controller that is currently active.

      I do not agree with the authors opinions, but his method works for the edge cases :)

Leave a Reply

Your email address will not be published. Required fields are marked *

*


eight * = 40

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>