提问



简而言之:



当导航控制器的后退按钮的标题发生变化时,在某些情况下旧标题会被卡住,并且不会显示新标题。这种情况仅在某些可重现的情况下发生,而在其他情况下则按其设计工作。


取决于硬件



  • 错误发生在iPhone 3G(iOS 4.2.1)和模拟器(iOS 5.1)

  • 使用相同的源代码,iPhone 4(iOS 5.1)
  • 上没有错误


取决于写入标题的单词



  • 当创建按钮时,它从我的自写创建方法获得与它自动获得的标题相同的单词(即导航控制器堆栈上的上一页的标题),以及另一个情况匹配,然后,当稍后尝试更改按钮的标题时,旧文本被卡住并且新标题不会出现。

  • 当在创建时按钮获得一个与其默认标题不同的标题的单词时,只要您不为其指定默认标题,其标题的每个后续更改都可以正常工作。

  • 如果在经过许多不同标题的成功更改后,您将单词放在按钮标题上,这是它的默认标题,那么这个单词就会卡住。以后的更改将不被接受(没有任何消息,只有在其他情况匹配时)



这取决于按钮在此期间是否不可见。



  • 如果在导航控制器堆栈上推送了另一个视图,那么带有缺陷按钮的旧页面将被新页面隐藏,并且当新页面再次从堆栈中弹出时再次使按钮可见,(当其他情况匹配时)旧文本被卡住,并且忽略了更改它的试验(没有任何消息)。

  • 如果按钮是新隐藏的,更改其标题永远不会有问题。我一直都在工作。



动画期间可以看到正确的标题



  • 当由于上述情况的组合而忽略了更改后退按钮标题的尝试时,无论如何,当按下此按钮并且页面滑动时,正确的标题会显示约0.3秒处理到右动画。在动画开始时,旧的标题被正确的标题取代,动画期间可以看到正确的标题。






详细说明



这是关于UINavigationController后退按钮上的文字。我根据新的语言设置更改了这个按钮标题。目前,我的应用程序在导航控制器堆栈中最多有3个视图控制器。它们中的每一个都是`UITableViewController的不同子类。


表1,名为GeneralTableVC是堆栈上的根视图。它没有后退按钮。它为用户提供了他在应用程序中存储的内容的摘要,并显示了带有设置按钮的工具栏。


导航控制器提供了在表1中可见的工具栏。它在表2和3中设置为不可见。此时工具栏中只有一个名为设置的按钮。触摸此设置按钮会将表2推入堆栈。


表2,名为SettingsTabVC有一个后退按钮,这是在模拟器中出现问题但在运行iOS 5.1的真正iPhone 4上运行正常的按钮。


通过触摸表2的第一行,将创建一个新表(表3)并将其推入堆栈。


表3,名为LangSelectTableVC也有一个后退按钮,但这个在两个设备,iPhone模拟器和真正的iPhone 4中工作得很好。


表3是一个语言选择表,显示所有可用语言的列表(目前只有英语和德语)。触摸行会立即更改设置。将重新绘制Activity视图(表3),并在几毫秒内,屏幕上的所有文本都以新语言显示。


重绘表本身没有问题,以及导航栏中的标题。但是后退按钮上的文本也必须翻译,这有点棘手。我在两个后退按钮上都完成了相同的技巧,并且它对于表3中可见的按钮工作正常,该按钮指向表2.但是使用完全相同的代码在模拟器中存在问题(但不是真实的iPhone),表2中的按钮指向表1。


我给你一些代码片段和一些截图,告诉你我做了什么以及发生了什么:





源码



ARC(自动引用计数)正在使用中。


我确实定义了一个重绘协议:


Protocols.h


#ifndef ToDo_Project_Protocols_h
#define ToDo_Project_Protocols_h

@protocol redrawProt
- (void) mustRedraw;
@end

#endif


这是表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


其他表的头文件SettingsTabVC.hLangSelectTabVC.h定义了相同的属性和相同的init函数


该计划从这里开始:


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
}


接下来是表1的实现(GeneralTableVC.m)。表2(SettingsTabVC.m)和表3(LangSelectTabVC.m)中的代码类似地相同。我没有展示那些实现协议UITableViewDataSource的代码部分。我认为这些部分对解释问题并不重要。


在此代码中,您将找到与NSLocalizedString(keyword,comment)完全相同的宏LocalizedString(keyword),它将关键字转换为所需的语言。我的这个宏的版本使用不同的bundel进行翻译(不是主要的包)


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





截图



在iPhone 5.1模拟器上启动应用程序会将表1(GeneralTableVC)放在屏幕上:


启动应用程序后,

在屏幕按钮的工具栏中,您可以在右侧找到设置按钮。按此按钮可在屏幕上显示下一个表格:





观看标题栏中的后退按钮。它显示文本摘要,这是正确的,因为上一个表标题是摘要。


现在我们选中第一行(Language English >):





一切安好。
现在让我们改变语言。选项卡German:


更改语言后,

哇!现在一切都用德语。即使后退按钮已从设置更改为Einstellungen。


让我们看看Einstellungen后退按钮:





现在差不多了;一切都变成了德国人。除了后退按钮之外的所有内容,仍然显示摘要而不是Überblick。我不明白为什么,因为当我在真正的iPhone 4上使用完全相同的源代码完成相同的步骤时,最后一个屏幕看起来像这样:





注意后退按钮上的文字。在真正的iPhone 4上,它是德语单词Überblick(这就是我想要的),但在模拟器中它是英文单词Summary。
这对我来说意味着,在某些手机(如我的iPhone 4)上,用户可以获得预期的效果,但也许在其他手机(可能是iPhone 4S)上,用户会看到错误的显示器。


有谁知道我的代码有什么问题?





编辑



 2012-04-06 09:04 +02:00(欧洲中部夏令时间)


我确实设法在另一台硬件上测试我的应用程序,旧的iPhone 3G(iOS 4.2.1)在旧的iPhone上,我的应用程序的行为方式与模拟器中的完全相同。在iPhone 4上运行相同的应用程序会产生不同的行为。


更确切地说:



  • 在iPhone 4(iOS 5.1)上:应用程序正在执行我想要的操作,没有任何错误行为。

  • 在模拟器上(iOS 5.1):应用程序在导航控制器后退按钮上显示错误的标题。

  • 在iPhone 3G(iOS 4.2.1)上:应用程序显示与模拟器中相同的错误行为。






 2012-04-07 10:14 +02:00(欧洲中部夏季时间)


通过观察iPhone 3G上的过渡,我喜欢一些有趣的东西,也许是有用的:当我用错误的文本选中按钮时,会发生以下情况:



  1. 错误的文字被正确的文字替换

  2. 在此替换之后,视图消失了动画(向右滑动),并且底层视图变得可见。此转换的持续时间约为0.3秒,在此短暂的时间间隔内,所有硬件iPhone和模拟器中都可以看到正确的文本。



但问题仍然是:为什么在iPhone 3G和模拟器中显示wront文本?为什么在iPhone 4中始终可以看到正确的文字?


对我而言,好像在同一个地方有两个按钮,一个在另一个上面。在iPhone 4中,我的自定义按钮位于前面,隐藏了旧的系统生成按钮,但在模拟器和iPhone 3G中,旧的systemgenerated按钮位于前面,隐藏了我的自定义按钮。但是:即使我隐藏的自定义按钮比系统生成的更大(更宽),也看不到它。仅当幻灯片动画开始时,我的按钮才会显示。





 2012-04-07 16:38 +02:00(欧洲中部夏令时间)


下一个有趣的事实


这就是现在发生的事情:


当按钮第一次出现时(第二次截图,见下文),我在其上添加了一个单词作为标题,这与系统之前的单词是一致的。然后用户选择一些动作,该按钮被另一个视图隐藏。在另一个用户操作后,按钮再次显示,现在它将获得一个新词作为标题(相同的意思,但新语言),但在iPhone 3G和模拟器上旧标题更强。新标题不会显示。旧标题贴在那里。


如果首先出现我在按钮上写一个单词作为标题,那就不会发生这种情况,这与系统生成的标题不同。如果第一个标题与默认标题不同,则稍后的更改将在所有iPhone和模拟器上执行。


这让我相信,iOS会做某种优化:如果,首次出现按钮,自定义标题与系统生成的标题相同,则稍后更改按钮标题将被忽略,但仅限于iPhone 3G和模拟器。在iPhone 4上,任何情况下都允许稍后更改。


但是在开头设置不同的标题以防止应用程序出现错误行为不是一种选择。

最佳参考


我怀疑你所看到的问题归结为模拟器上发生的序列和真实硬件之间的微妙时序问题。


视图控件不一定在viewDidLoad中实例化,因此您应该等到viewWillAppear设置标题值(等)。


以下是建设性的意思,请按照其意图采取的精神:


如果不仔细检查您的代码,我怀疑您想要实现的目标可以更加确定地实现。你试图做的事情并不困难或不寻常,但我担心你的代码看起来不必要地复杂 - 可能是因为你试图解决这些时间问题。


看一些简单的示例和教程,并查看简化代码,以便它不使用标志来跟踪状态(mustRedrawMyself),因为这不是必需的。请记住,在viewWillAppear之前不要设置视图/控件的属性,并看看你是如何进行的。


您可能还想查看内置的本地化支持,如果您还没有。


祝你好运。

其它参考1


我会尝试这个:


移动SELF REDRAW如果并执行到AFTER,您将其更新为viewWillAppear中的本地化:


在更改标题之前,您正在告诉REDRAW视图,因此,标题更新不是重绘的一部分。它正在做它应该做的......而我的猜测是在一些硬件/模拟器上的定时操作更快,所以在调用重绘之后你做的更新是在一些硬件/模拟器上完成绘制之前发生的,但是之前没有完成其他的吸引力正在发生。


尝试将操作顺序更改为以下内容,并让我知道它是如何工作的。


- (void) viewWillAppear:(BOOL)animated {
     // this is an important method! Maybe here is the reason for 
 // my problem!  
[super viewWillAppear:animated];
// 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; 

 // 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];   
}  

} 

其它参考2


Apple支持确实回答



我联系了Apple对该问题的支持,他们确实回答了。


问题是,导航栏包含导航控制器堆栈上所有视图的后退按钮,所有这些按钮需要同时更新。在viewWillAppear-Methods中更新位于堆栈上的视图是好的,但尝试更新此处的后退按钮并不是一个好主意。





解决方案:


扩展UIViewController的接口:


@interface UIViewController (extended)
    - (NSString *)localizedKey;
@end


对于将视图放在UINavigationController的堆栈上的每个UIViewController实现此方法:


- (NSString*) localizedKey {
    return @"a title-keyword";
}


不要在任何UIViewControllers中乱用UIBarButtonItemself.navigationItem.backBarButtonItem


如果需要更改标题,请使用此代码片段对所有后退按钮执行此操作(请记住:LocalizedString(key)是一个类似于NSLocalizedString(key,comment)的自编写宏):


NSArray* vcs = [self.navigationController viewControllers];
for (UIViewController* vc in vcs) {
    vc.navigationItem.backBarButtonItem.title = LocalizedString([vc localizedKey]);
    vc.title = LocalizedString([vc localizedKey]);
}





Apple支持的逐字回答:



  我们在错误的时间强制更新导航栏。请注意每个视图控制器中的所有视图都能正确更新。因此导航栏需要特别注意才能获得我们想要的内容。

  
  要使其工作,您需要立即将后退按钮更改为堆栈中的所有视图控制器(在用户选择语言时),而不是通过viewWillAppear将它们各自显示。

  
  这需要能够以公共方式获取该按钮的本地化密钥。我向UIViewController引入了一个类别来轻松采用这个:

  
  @interface UIViewController(扩展)

   - (NSString *)localizedKey;

  @结束

  
  然后您的LangSelectTabVC类可以立即更改所有后退按钮。此方法将使按钮标题正确重绘。

  
  所以在viewWillAppear中,你不必更新每个后退按钮。在那里做它似乎为UIKit捕获更新太迟了。你也在更新时重新创建一个新的后退按钮。这不是必需的,只是拿当前的一个并改变它的标题:

  
  NSArray * vcs=[[self.navigationController viewControllers]];

  for(UIViewController * vc in vcs)

  {点击
      vc.navigationItem.backBarButtonItem.title=LocalizedString([[vc localizedKey]]);

      vc.title=LocalizedString([[vc localizedKey]]);

  }

  
  我已经附加了一个显示此解决方法的修改过的项目。