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
It depends on the word that is written to the title
It depends on whether the button was invisible in the meantime or not.
The correct title is visible during animation
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:
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
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?
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:
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With