29

简而言之:

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

这取决于硬件

  • 该错误发生在 iPhone 3G (iOS 4.2.1) 和模拟器 (iOS 5.1) 上
  • 使用相同的源代码在 iPhone 4 (iOS 5.1) 上没有错误

这取决于写在标题上的单词

  • 当按钮被创建时,它从我自己编写的创建方法中获得与自动获得的标题相同的单词(即导航控制器堆栈上上一页的标题),并且当其他情况匹配时,然后,稍后尝试更改按钮的标题时,旧文本被卡住,新标题不会显示。
  • 当在创建时按钮获得一个与其默认标题不同的单词作为标题时,只要您不为其分配默认标题,其标题的每次更改都可以正常工作。
  • 如果在对许多不同的标题进行了多次成功更改后,您将单词放在按钮标题上,这是它的默认标题,那么这个单词就会被卡住。以后的更改将不被接受(没有任何消息,并且只有在其他情况匹配时)

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

  • 如果另一个视图被推送到导航控制器堆栈上,那么带有缺陷按钮的旧页面被新页面隐藏,当新页面再次从堆栈中弹出时,按钮再次可见,(并且当其他情况匹配)然后旧文本被卡住并且尝试更改它被忽略(没有任何消息)。
  • 如果按钮是较新的隐藏,更改其标题永远不会有问题。我总是工作。

正确的标题在动画期间可见

  • 当由于上述情况的组合而忽略更改后退按钮标题的尝试时,无论如何,当点击此后退按钮并且页面的向右滑动动画时,正确的标题仍然可见约 0.3 秒处理。在动画开始时,旧的卡住标题被正确的标题替换,并且正确的标题在动画期间可见。

详细说明

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

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

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

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

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

表 3,命名LangSelectTableVC也有一个后退按钮,但是这个在 iPhone 模拟器和真正的 iPhone 4 两种设备上都很好用。

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

重绘表格本身是没有问题的,导航栏的标题也是如此。但是后退按钮上的文字也必须翻译,这有点棘手。我在两个后退按钮上都做了同样的技巧,它适用于表 3 上指向表 2 的可见按钮。但是使用相同的代码,模拟器中存在问题(但不是真实的iPhone)使用表 2 上的按钮指向表 1。

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


源代码

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

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

协议.h

#ifndef ToDo_Project_Protocols_h
#define ToDo_Project_Protocols_h

@protocol redrawProt
- (void) mustRedraw;
@end

#endif

这是表 1 的标题:

通用表VC.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定义相同的属性和相同的初始化函数

程序从这里开始:

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 的那些代码部分。我认为这些部分对于解释问题并不是很重要。

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

通用表VC.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) 显示在屏幕上:

启动应用程序后

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

设置屏幕

观看标题栏中的后退按钮。它显示文本“Summary”,这是正确的,因为之前的表格标题是“Summary”。

现在我们进入第一行(“ Language English >”):

在改变语言之前

一切安好。现在让我们更改语言。“”标签German

改变语言后

哇!现在一切都是德语。甚至后退按钮也从“设置”更改为“Einstellungen”。

让我们点击那个“Einstellungen”后退按钮:

后退按钮在模拟器中的语言错误

现在几乎一切都很好。一切都变成了德语。除了后退按钮之外的所有内容,它仍然显示“摘要”而不是“Überblick”。而且我不明白为什么,因为当我在我的真实 iPhone 4 上使用完全相同的源代码执行完全相同的步骤时,最后一个屏幕看起来像这样:

真实 iPhone 上的正确语言

注意后退按钮上的文字。在真正的 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 和 Simulator 会显示错误的文本?为什么在 iPhone 4 中总是可以看到正确的文本?

在我看来,就好像在同一个地方有两个按钮,一个接一个。在 iPhone 4 中,“我的”自定义按钮在前面,隐藏了旧的系统生成按钮,但在模拟器和 iPhone 3G 中,旧的系统生成按钮在前面,隐藏了我的自定义按钮。但是:即使我隐藏的自定义按钮比系统生成的更大(更宽),也没有任何可见的。只有当滑出动画开始时,我的按钮才可见。


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

下一个有趣的事实:

这是到目前为止发生的事情:

当按钮第一次出现时(第二张截图,见下文),我在上面放了一个词作为标题,这与它之前从系统中变成的词相同。然后用户选择了一些动作,这个按钮被另一个视图隐藏了。在另一个用户操作之后,按钮再次显示,它现在应该得到一个新词作为标题(相同的含义,但新的语言),但在 iPhone 3G 和模拟器上,旧标题是“更强”。不会显示新标题。旧标题就在那里。

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

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

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

4

3 回答 3

2

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

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

以下是建设性的意思,请本着它的意图:

如果不详细检查您的代码,我怀疑您尝试实现的目标可以更确定地实现。您正在尝试做的事情并不难或不寻常,但恐怕您的代码看起来不必要地令人费解 - 可能是因为您试图解决这些时间问题。

查看一些简单的示例和教程,并简化您的代码,使其不使用标志来跟踪状态(mustRedrawMyself),因为这不是必需的。记住在 viewWillAppear 之前不要设置视图/控件的属性,然后看看你是怎么做的。

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

祝你好运。

于 2012-04-07T09:34:16.307 回答
0

我会试试这个:

将 SELF REDRAW if 和执行移动到 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];   
}  

} 
于 2012-04-09T20:59:36.557 回答
0

苹果支持确实回答了

我就这个问题联系了 Apple 支持,他们确实回答了。

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


解决方案:

扩展 UIViewController 的接口:

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

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

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

不要乱用UIBarButtonItem任何self.navigationItem.backBarButtonItemUIViewControllers。

如果需要更改标题,请使用此代码片段为所有后退按钮执行此操作(请记住: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([vclocalizedKey]);
vc.title = LocalizedString([vc 本地化键]);
}

我附上了一个显示此解决方法的修改项目。

于 2012-04-20T11:06:39.050 回答