In a nutshell:
When the title of a back-button of a navigation controller gets changed, in some cases the old title is stuck and the new title will not be displayed. This happens only in some reproduceable situations, while it works as designed in other situations.
It depends on the hardware
- The error happens on iPhone 3G (iOS 4.2.1) and in the simulator (iOS 5.1)
- With identical sourcecode there is no error on iPhone 4 (iOS 5.1)
It depends on the word that is written to the title
- When the button is created, and it gets from my selfwritten creating-method the same word as title that it would have got automatically (i.e. the title of the previous page on the navigation controller's stack), and when the other circumstances match, then, when trying to change the button's title at a later moment, the old text is stuck and the new title will not show up.
- When at creation time the button gets a word as title that is different from its default-title, then every later changes of its title works fine, as long as you don't assign the default-title to it.
- If, after a lot of successfull changes with many different titles, you put the word on the buttons title, that was its default title, then this word is stuck. Later changes will not be accepted (without any message, and only if the other circumstances match)
It depends on whether the button was invisible in the meantime or not.
- If another view was pushed on the navigation controllers stack, so that the old page with the flawed button became hidden by the new page, and when the new page was popped from the stack later again which makes the button visible again, (and when the other circumstances match) then the old text was stuck and the trial to change it is ignored (without any message).
- If the button was newer hidden, changing its title never is no problem. I works always.
The correct title is visible during animation
- When the attempt to change the back-button's title was ignored due to the combination of the circumstances described above, the proper title anyhow becomes visible for about 0.3 seconds when this back-button is hit and the page's slide-to-right-animation is processed. At the beginning of the animation the old stuck title is replaced by the proper title, and the correct title is visible during the animation.
Detailed description
It's about the text on a UINavigationController
's back button. I change this buttons title in dependence of new language-settings. At the moment my app has a maximum of 3 view controllers in the navigation controllers stack. Each of them is a different subclass of `UITableViewController.
Table 1, named GeneralTableVC
is the root view on the stack. It has no back button. It gives the user a summary of what he has stored inside the app and it displayes a toolbar with a settings-button.
It is the navitation controller who provides this Toolbar that is visible in Table 1. It is set to invisible in Table 2 and 3. At the moment there is only one button in that toolbar named "Settings". Touching this Settings-Button will push Table 2 onto the stack.
Table 2, named SettingsTabVC
has a back button, and this is the one that makes problems in the simulator but works fine on my real iPhone 4 running iOS 5.1.
By touching the first row of Table 2 a new Table (Table 3) will be created and pushed onto the stack.
Table 3, named LangSelectTableVC
also has a back button, but this one works pretty fine in both devices, iPhone simulator and real iPhone 4.
Table 3 is a language selection table that displayes a list of all available languages (at the moment just english and german). Touching a row changes settings immediately. The active view (Table 3) will be redrawn, and within a few milliseconds all texts on screen appear in the new language.
Redrawing the table itself is no problem, as well as the title in the navigation bar. But the text on the back button must be translated too, and this is a little bit tricky. I have done the very same trick on both back-buttons, and it works fine for the button visible on Table 3 who is directing to Table 2. But with the very same code there is a problem in the simulator (but not on a real iPhone) with the button on Table 2 who is directing to Table 1.
I give you some code-snippets and some screenshots to show you what I've done and what is happening:
Sourcecode
ARC (automatic reference counting) is in use.
I did define a redraw-Protocol:
Protocols.h
#ifndef ToDo_Project_Protocols_h
#define ToDo_Project_Protocols_h
@protocol redrawProt
- (void) mustRedraw;
@end
#endif
This is the header of Table 1:
GeneralTableVC.h
#import <UIKit/UIKit.h>
#import "Protocols.h"
// some other imports
@interface GeneralTabVC : UITableViewController <redrawProt>
@property id<redrawProt> parent;
@property Boolean mustRedrawMyself;
@property NSString* backTitle;
@property UIBarButtonItem* myBackButton;
@property UIBarButtonItem* parBackButton;
- (id) initWithParent:(id<redrawProt>)par andBackTitle:(NSString*)bT andBackButton:(UIBarButtonItem*)bB;
@end
The header files of the other Tables, SettingsTabVC.h
and LangSelectTabVC.h
define the same properties and an identical init-function
The program starts here:
part of AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// some code
GeneralTabVC* genTabCon = [[GeneralTabVC alloc] initWithParent:nil andBackTitle:nil andBackButton:nil];
UINavigationController* navCon = [[UINavigationController alloc] initWithRootViewController:genTabCon];
// some other code
}
Next comes the implementation of Table 1 (GeneralTableVC.m
). The Code in Table 2 (SettingsTabVC.m
) and Table 3 (LangSelectTabVC.m
) is analogously the same. I don't show those parts of code that implements the protocol UITableViewDataSource. I think those parts are not really important for explaining the problem.
In this code you will find the macro LocalizedString(keyword)
which does exactly the same as NSLocalizedString(keyword,comment)
, which is translating the keyword into the desired language. My version of this macro uses a different bundel for translation (not the main bundle)
GeneralTableVC.m
#import "GeneralTabVC.h"
#import "SettingsTabVC.h"
#define MYTITLE @"summary"
id<redrawProt> parent;
Boolean mustRedrawMyself;
NSString* backTitle;
UIBarButtonItem* myBackButton;
UIBarButtonItem* parBackButton;
@interface GeneralTabVC ()
@end
@implementation GeneralTabVC
@synthesize parent, mustRedrawMyself, backTitle, myBackButton, parBackButton;
- (void) mustRedraw {
self.mustRedrawMyself = YES;
}
- (void) redraw {
if ((self.parBackButton) && (self.backTitle)) {
// Important!
// here I change the back buttons title!
self.parBackButton.title = LocalizedString(self.backTitle);
}
if (self.parent) {
[self.parent mustRedraw];
}
self.title = LocalizedString(MYTITLE);
[self.tableView reloadData];
self.mustRedrawMyself = NO;
}
- (id) initWithParent:(id<redrawProt>)par andBackTitle:(NSString*)bT andBackButton:(UIBarButtonItem *)bB {
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
self.parent = par;
self.mustRedrawMyself = NO;
self.backTitle = bT;
self.parBackButton = bB;
}
return self;
}
- (void) toolbarInit {
// this method exists only in Table 1, not in other tables
// it creates a UIBarButtonItem, adds it to self.toolbarItems
// and makes it visible
}
- (void)SettingsAction:(id)sender {
// this method exists only in Table 1, not in other tables
// it will be executed after the user tabs on the settings-
// button in the toolbar
SettingsTabVC* setTabCon = [[SettingsTabVC alloc] initWithParent:self andBackTitle:MYTITLE andBackButton:self.myBackButton];
[self.navigationController pushViewController:setTabCon animated:YES];
}
- (void) viewDidLoad {
[super viewDidLoad];
self.title = LocalizedString(MYTITLE);
// I want an Edit-Button. Localization of this button is
// not yet done. At the moment is uses the systems language,
// not the apps language.
self.navigationItem.rightBarButtonItem = self.editButtonItem;
[self toolbarInit];
}
- (void) viewWillAppear:(BOOL)animated {
// this is an important method! Maybe here is the reason for
// my problem!
[super viewWillAppear:animated];
// When ever this controllers view is going to appear, and
// when ever it is necessary to redraw it in a new language,
// it will redraw itself:
if (self.mustRedrawMyself) {
[self redraw];
}
// And here comes the buggy back button:
// When ever this controllers view is going to appear,
// a new back button will be created with a title in the
// new language:
UIBarButtonItem* BB = [[UIBarButtonItem alloc] init];
BB.title = LocalizedString(MYTITLE);
self.myBackButton = BB;
self.navigationItem.backBarButtonItem = self.myBackButton;
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// show toolbar:
[self.navigationController setToolbarHidden:NO animated:YES];
}
// next methods are about InterfaceOrientation and the
// UITableViewDataSource protocoll. They are not important
// for the problem.
// but maybe the very last method is important. It comes in
// different versions in the three implementation files:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// This is the version of GeneralTableVC.m (Table 1)
// It does nothing (at the actual stage of expansion, in later
// versions it will start the main business logic of this app)
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// This is the version of SettingsTableVC.m (Table 2)
// Tabbing onto row 0 of section 0 will push the
// language-selection-table (Table 3) on screen:
if (indexPath.section == 0) {
if (indexPath.row == 0) {
// create Table 3:
LangSelectTabVC* langTabCon = [[LangSelectTabVC alloc] initWithParent:self andBackTitle:MYTITLE andBackButton:self.myBackButton];
[self.navigationController pushViewController:langTabCon animated:YES];
} else {
// do something else (nothing at this stage of expansion)
}
} else {
// do something else (nothing at this stage of expansion)
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// This is the version of LangSelectTableVC.m (Table 3)
// here I do some magic to select and store the new language.
// Part of this magic is transforming indexPath.row
// into a valid language-code, putting it into the
// settings-object, and registering this object to
// NSUserDefaults
}
@end
Screenshots
Starting the app on iPhone 5.1 simulator will put Table 1 (GeneralTableVC
) on screen:
In the toolbar on the screens button, on its right side, you find a settings-button. Pressing this button brings the next table on screen:
Watch the back button in the title bar. It displays the text "Summary", which is correct, since the previous table title was "Summary".
Now we tab onto the first row ("Language English >
"):
Everything is fine.
Now let's change the language. Tab on "German
":
Wow! Everything is in German now. Even the back button has changed from "Settings" to "Einstellungen".
Lets tab on that "Einstellungen" back button:
Almost everthing is fine now; everything has changed into german. Everything but the back button, which still says "Summary" instead of "Überblick". And I do not understand why, because when I do exactly the same steps with exactly the same sourcecode on my real iPhone 4, the last screen looks like this:
Mind the text on the back-button. On a real iPhone 4 it is the german word "Überblick" (which is what I want), but in the simulator it is the english word "Summary". And this means to me, on some phones (like my iPhone 4) the user gets what is expected, but maybe on some other phones (maybe iPhone 4S) the user gets a buggy display.
Has anybody an idea what is wrong with my code?
EDITs
Edit: 2012-04-06 09:04 +02:00 (central european summer time)
I did manage to test my app on an other piece of hardware, an old iPhone 3G (iOS 4.2.1) On the old iPhone my app is behaving exactly the same way like in the simulator. Running the same app on iPhone 4 produces a different behaviour.
To be more precise:
- On iPhone 4 (iOS 5.1): App is doing what I want, no faulty behavior.
- On Simulator (iOS 5.1): App displays wrong title on a navigation controllers back button.
- On iPhone 3G (iOS 4.2.1): App shows the same faulty behaviour as in the simulator.
Edit: 2012-04-07 10:14 +02:00 (central european summer time)
By watching the transition on the iPhone 3G, I fond out something interesting and maybe helpfull: When I tab on the button with the wrong text, the following happens:
- The wrong text is replaced by the correct text
- After this replacement the view dissapears animated (sliding to the right) and the underlaying view becomes visible. This transition has a duration of aproximately 0.3 seconds, and in this short interval the correct text is visible in all hardware-iPhones and in the simulator too.
But the question still is: Why is the wront text displayed in iPhone 3G and Simulator? Why is the correct text always visible in iPhone 4?
To me it looks, as if there was two buttons at the same place, one over the other. In iPhone 4 "my" custom button is in front, hiding the old systemgenerated button, but in the simulator and in iPhone 3G the old systemgenerated button is in front, hiding my custom button. But: Even if my hidden custom button is bigger (wider) than the systemgenerated, nothing of it is visible. Only when the slide-out animation starts, my button becomes visible.
Edit: 2012-04-07 16:38 +02:00 (central european summer time)
Next interesting fact:
This is what happened until now:
When the button appers for the first time (2nd screenshot, see below), I put a word as title on it, that is identic to the word it would have become before from the system. Then the user selects some action, and this button is hidden by another view. After another user-action the button is revealed again, it now it shall get a new word as title (same meaning, but new language), but on iPhone 3G and on the simulator the old title is "stronger". The new title will not be displayed. The old title sticks there.
This does not happen if at first appearence I write a word as title onto the button, that is different from the systemgenerated title. If the first title is different from the default-title, a later change will be executed on all iPhones and on the simulator.
That makes me believe, that iOS does some kind of "optimization": If, at first appearance of the button, the custom title is identic to the systemgenerated title, than a later change of the buttons title will be ignored, but only on iPhone 3G and simulator. On iPhone 4 a later change will be allowed in any case.
But setting a different title at the beginning to prevent the app from its faulty behaviour is not an option.
I suspect the issues you're seeing are down to subtle timing issues between the sequence that occurs on the Simulator, and real hardware.
The view controls are not necessarily instantiated in viewDidLoad, so you should wait until viewWillAppear to set title values (etc).
The following is meant constructively, please take it in the spirit it's intended:
Without reviewing your code in detail, I suspect what you are trying to achieve could be achieved more deterministically. What you're trying to do isn't hard or unusual, but I'm afraid your code looks unnecessarily convoluted - possibly as a result of you trying to fix these timing issues.
Take a look at some simple examples and tutorials, and look to simplify your code so it doesn't use flags to track state (mustRedrawMyself) as this shouldn't be necessary. Remember to not set properties of views / controls until viewWillAppear, and see how you get on.
You might also want to look at the inbuilt support for localisation, if you're not already.
Good luck.
I would Try this:
Move the SELF REDRAW if and execution to AFTER where you are updating it to your localization in your viewWillAppear:
You are telling the view to REDRAW before you change the title, and as such, the title update is not part of the redraw. Its doing exactly what it should.. and my guess is timing operations on some hardware/emulator are faster, so that the update you do after the call to redraw is happening before the draw completes on some hardware/emulator, but not finishing prior to the draw happening on other.
try changing the order of operations to the following, and let me know how if that works.
Apple support did answer
I contacted Apple support for that issue and they did answer.
The problem was, that the navigation bar holds all back-buttons for views on the navigation controllers stack and all this buttons needs to be updated at the same time. Updating the views that lay on the stack within their viewWillAppear-Methods is good, but trying to update the back-button at this place is no good idea.
The solution:
Extend the interface of UIViewController:
For each UIViewController that puts a view on the UINavigationController's stack implement this method:
Do not mess around with
UIBarButtonItem
orself.navigationItem.backBarButtonItem
in any of the UIViewControllers.When if comes that titles need to be changed, do it for ALL back buttons with this snippet of code (remember:
LocalizedString(key)
is a self-written macro similar toNSLocalizedString(key,comment)
):Verbatim answer of Apple Support: