Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App is behaving different on iPhone 5.1 simulator and real iPhone 4 with iOS 5.1

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:

after starting the app

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:

settings-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 >"):

before changing language

Everything is fine. Now let's change the language. Tab on "German":

after changing language

Wow! Everything is in German now. Even the back button has changed from "Settings" to "Einstellungen".

Lets tab on that "Einstellungen" back button:

back button has wrong language in simulator

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:

correct langugage on real iPhone

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:

  1. The wrong text is replaced by the correct text
  2. 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.

like image 542
Hubert Schölnast Avatar asked Apr 05 '12 14:04

Hubert Schölnast


1 Answers

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.

like image 117
Snips Avatar answered Nov 11 '22 11:11

Snips