iOS 7中UIBarButtonItem外观出现问题,这可能是苹果的一个bug吗?

9
我之前看到了一篇文章,链接在这里:iOS 6中的用户界面自定义。该文章展示了如何在iOS 6上进行自定义。自从那篇文章发表以来,我就写了一些使用这种技术的应用程序,它们都很简单,没有什么魔法。
然而,现在我需要更新其中一个应用程序,在iOS 7下它无法正常工作。看起来UIBarButtonItems的自定义在第一次显示视图时无效。如果我关闭视图再重新打开它,所有东西就正常了。以下是出现的问题:
第一次视图呈现: enter image description here 第二次: enter image description here 我在他的示例代码、我的代码和我编写的测试应用程序中都遇到了这个问题。相关的代码如下:
// Customizing the Back Bar Buttons
UIImage * btBack_30 = [[UIImage imageNamed:@"btBack_30"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
UIImage * btBack_24 = [[UIImage imageNamed:@"btBack_24"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 12, 0, 5)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_30 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_24 forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];

正如您所看到的,这并没有什么特别之处,非常标准,但我找不到任何理由或解释,为什么在iOS 7中无法正常工作。该代码在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中执行。

希望有人看到这个问题并能提供解决方案。感谢任何帮助!

**注意:有人提出这不是苹果的bug,而是设计问题。我不是说这是苹果的问题,更可能是我的问题,但如果您运行以下任一示例或复制并粘贴下面的代码,则可以明显看出第一次它不正确地工作,随后的时间它才可以。这使我相信api调用是有效的,但它们可能存在错误,或者我错过了需要完成的某些操作。

****更新4:

根据FruityGeek的建议,我在我的示例中将MyAppDelegate的init方法中的代码更改为以下内容,但仍然没有运气:

- (instancetype)init
{
    self = [super init];
    if (self)
    {
        //Other UIAppearance proxy calls go here

        [[UIBarButtonItem appearance] setTitleTextAttributes:
         [NSDictionary dictionaryWithObjectsAndKeys:
          //[UIColor colorWithRed:220.0/255.0 green:104.0/255.0 blue:1.0/255.0 alpha:1.0],
          [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0],
          UITextAttributeTextColor,
          //[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
          [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8],
          UITextAttributeTextShadowColor,
          [NSValue valueWithUIOffset:UIOffsetMake(0.5, 0.5)],
          UITextAttributeTextShadowOffset,
          [UIFont systemFontOfSize:12.0],
          UITextAttributeFont,
          nil]
                                                    forState:UIControlStateNormal];

        // Customizing the Back Bar Buttons
        //ios6 uses whole button background image
        UIImage * btBack_30 = [[UIImage imageNamed:@"btBack_30"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
        UIImage * btBack_24 = [[UIImage imageNamed:@"btBack_24"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 12, 0, 5)];
        [[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_30 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_24 forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];

        if ([[UIDevice currentDevice].systemVersion integerValue] >= 7)
        {
            //ios7 needs additional chevron replacement image
            UIImage * chevronReplacement = chevronReplacement = [btBack_30 imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
            UIImage * chevronTransitionMaskReplacement = chevronTransitionMaskReplacement = [btBack_30 imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
            [[UINavigationBar appearance] setBackIndicatorImage:chevronReplacement];
            [[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:chevronTransitionMaskReplacement];
        }
    }
    return self;
}

**** 更新 3:**

我添加了一个 Dropbox 链接,链接中包含一个示例项目。这是除了上面发布的简单应用之外的另一个选择。两者均可在 iOS 6 和 iOS 7 模拟器中构建和运行。在 iOS 6 中,一切都如预期般正常。在 iOS 7 中,如果您单击表格单元并呈现下一个视图,则不会显示自定义返回按钮。如果您返回并再次呈现,则按钮将出现。

我已经试了几天了,所以希望其他人能看到它并告诉我我错过了什么。

https://www.dropbox.com/s/oi1bh3emvtbmms0/NavigationBarDemo.zip

这可能很傻,但是否与我的图像有关?我将尝试使用不同的图像进行测试并发布更新。

  • 已经尝试使用不同的图像,但没有任何区别,也尝试使用上面发布示例中使用的图像。这只是一种不太可能的尝试,但由于似乎没有更好的想法,因此值得一试。

**** 更新2:**

我已经在另一个测试应用程序中尝试了此功能,并将代码移动到了应用程序委托的 init 方法中,但仍然无法正常工作。我在这里发布了这个问题,在原始作者的链接页面上也有,而且还在另一个论坛上发帖询问过,但目前还没有解决方案。

我想知道这是否可能是苹果的一个 bug?

**** 更新 1:**

将代码从 didFinishLaunchingWithOptions 移动到 willFinishLaunchingWithOptionsinit 中,但似乎仍然无法正常工作。

***** INIT METHOD FROM AppDelegate.m

- (id)init
{
    // Create resizable images
    UIImage *gradientImage44 = [[UIImage imageNamed:@"navBar_44"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
    UIImage *gradientImage32 = [[UIImage imageNamed:@"navBar_32"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];

    // Set the background image for *all* UINavigationBars
    [[UINavigationBar appearance] setBackgroundImage:gradientImage44 forBarMetrics:UIBarMetricsDefault];
    [[UINavigationBar appearance] setBackgroundImage:gradientImage32 forBarMetrics:UIBarMetricsLandscapePhone];

    // Customize the title text for *all* UINavigationBars
    [[UINavigationBar appearance] setTitleTextAttributes:
     [NSDictionary dictionaryWithObjectsAndKeys:
      [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0],
      UITextAttributeTextColor,
      [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8],
      UITextAttributeTextShadowColor,
      [NSValue valueWithUIOffset:UIOffsetMake(1, 1)],
      UITextAttributeTextShadowOffset,
      [UIFont boldSystemFontOfSize:18.0],
      UITextAttributeFont,
      nil]];

    // Customizing the NavBar Buttons
    UIImage * button30 = [[UIImage imageNamed:@"btButton_30"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
    UIImage * button24 = [[UIImage imageNamed:@"btButton_24"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
    [[UIBarButtonItem appearance] setBackgroundImage:button30 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    [[UIBarButtonItem appearance] setBackgroundImage:button24 forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];

    [[UIBarButtonItem appearance] setTintColor:[UIColor whiteColor]];

    [[UIBarButtonItem appearance] setTitleTextAttributes:
     [NSDictionary dictionaryWithObjectsAndKeys:
      //[UIColor colorWithRed:220.0/255.0 green:104.0/255.0 blue:1.0/255.0 alpha:1.0],
      [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0],
      UITextAttributeTextColor,
      //[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
      [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8],
      UITextAttributeTextShadowColor,
      [NSValue valueWithUIOffset:UIOffsetMake(0.5, 0.5)],
      UITextAttributeTextShadowOffset,
      [UIFont systemFontOfSize:12.0],
      UITextAttributeFont,
      nil]
                                                forState:UIControlStateNormal];

    // Customizing the Back Bar Buttons
    UIImage * btBack_30 = [[UIImage imageNamed:@"btBack_30"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
    UIImage * btBack_24 = [[UIImage imageNamed:@"btBack_24"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 12, 0, 5)];
    [[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_30 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    [[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_24 forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];

    return [super init];
}

瞎猜——视图最初呈现后,您是否要转到外观代理?该代码相对于建立根视图控制器在哪里? - Tommy
我在AppDelegate.m文件中的-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中实现了这个功能。 - LilMoke
将您的演示项目放在Dropbox上,以便他人查看(并修复代码)。 - David H
有只小鸟告诉我,你应该使用最新的7.1测试版重新进行测试... :-) - David H
有任何解决方案吗?似乎在2019年的iOS 12中仍然存在问题! - Sunkas
5个回答

5

看起来像是苹果的一个bug,你应该使用错误报告工具来提交一个bug。话虽如此,我可以给您提供一个相对简单的解决方法:将以下代码添加到您的RecipetTableViewController中:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = @"Recipe Book";

    UIBarButtonItem *it = [[UIBarButtonItem alloc] initWithTitle:self.title style:UIBarButtonItemStylePlain target:nil action:NULL];
    UIImage * btBack_30 = [[UIImage imageNamed:@"btBack_30"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
    [it setBackButtonBackgroundImage:btBack_30 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    self.navigationItem.backBarButtonItem = it;
}

编辑:如果您有兴趣,可以复制此错误,引用此错误的错误越多,苹果修复它的可能性就越大:

错误:15506447

状态:打开产品:iOS

2013年11月19日下午3:53

摘要:设置UIBarButtonItem外观代理的后退栏按钮项没有任何影响,直到第二次出现该按钮。

重现步骤:在appDelegate中,在任何东西出现之前添加以下语句:

UIImage * gradientImage44 = [[UIImage imageNamed:@"navBar_44"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)]; UIImage *gradientImage32 = [[UIImage imageNamed:@"navBar_32"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)]; //为所有UINavigationBars设置背景图像 [[UINavigationBar appearance] setBackgroundImage:gradientImage44 forBarMetrics:UIBarMetricsDefault]; [[UINavigationBar appearance] setBackgroundImage:gradientImage32 forBarMetrics:UIBarMetricsLandscapePhone];

期望结果:当首次推入视图控制器时,其返回按钮中有图像。

实际结果:第一次出现时,没有图像。再次推视图,它就在那里了。实际上,当您第一次单击按钮时,它确实会出现,但是当按钮首次出现时不会出现。

版本:Xcode 5.0.1,iOS 7.0.3

注释:在导航控制器的根视图控制器中添加此内容可以使其正常工作:

  • (void)viewDidLoad { [super viewDidLoad]; self.title = @"Recipe Book"; UIBarButtonItem *it = [[UIBarButtonItem alloc] initWithTitle:self.title style:UIBarButtonItemStylePlain target:nil action:NULL]; UIImage *btBack_30 = [[UIImage imageNamed:@"btBack_30"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)]; [it setBackButtonBackgroundImage:btBack_30 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; self.navigationItem.backBarButtonItem = it; }

附加演示项目显示了问题。

配置:

附件:已成功上传“DynamicsCatalog.zip”。

编辑:我很高兴地说,再次在错误报告程序中输入错误有时确实有效!


1
耶!!!我很高兴至少有人认识到他们看到了它!!太棒了,谢谢!!我会尝试你的解决方法,但它需要进入我的应用程序,所以我会看看它是否有效。实际上,我早些时候就做到了这一点,但不想以这种方式做,因为我的应用程序中有多个TableViewControllers。我认为问题出在我使用外观调用的方式上。无论如何,我会尝试一下,再次感谢!! - LilMoke
我尝试了书中的每一个技巧,还有一些额外的方法,但都没有用。根本问题在于,当Apple发现需要时,它会动态地创建该按钮,可能是在他们没有考虑应用外观样式的特殊位置上。我无法想到任何方式来限制后退栏按钮的创建,除非使用方法交换。此外,如果您有多个表视图和选项卡控制器,则可以在每个tableViewControllers首次加载时让tabBarController执行此操作,或者您可以使用一个小的UITableView子类来实现这一点。 - David H
哇,太高兴收到你的消息了。我读了又读,试了各种方法,但还是感到很疯狂。很高兴你看到了这个问题。我已经向苹果开了一个支持票据,所以我会看看情况如何。目前,我使用了你提出的方法,并将其放在我的每个TableViewController中。嘿,快问一下,也许我脑子抽了,但为什么按钮的标题显示为“返回”,而不是呈现视图的名称?我按以下方式设置它:TVCCMSTopLevelMenu * svc; svc.title = homeIconObj.sTitle; - LilMoke
你在某些视图控制器中看到的“后退”标题实际上是上面那个视图控制器中的 backButton 项!是啊,我也会对此感到困惑 - 我处理这个问题已经多年了。就好像当前视图控制器的 backBarButton 是从上面的视图控制器“借来”的一样。 - David H
我的回答关于iOS 7自定义返回按钮有一个补丁可以修复苹果的显示bug(这确实是一个bug)。 - Carl Lindberg
我有同样的问题。在 iOS 12 中似乎仍然存在问题!有任何新的解决方案吗? - Sunkas

0
我通过在将要消失的视图(viewWillDisappear)方法中对推动到我想要返回按钮的视图进行后退按钮自定义来解决了这个问题。原因是因为上一个视图“拥有”返回按钮,而不是当前视图。
-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear: animated];
    UIImage * backButtonImage = [[UIImage imageNamed:@"back.png"] 
                      resizableImageWithCapInsets:UIEdgeInsetsMake(6, 15, 6, 7)];

    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:self.title 
                      style:UIBarButtonItemStylePlain target:nil action:NULL];
    [buttonItem setBackButtonBackgroundImage:backButtonImage forState:UIControlStateNormal
                      barMetrics:UIBarMetricsDefault];
    self.navigationItem.backBarButtonItem = buttonItem;

}

0

在iOS7中,您必须以不同的方式处理自定义返回按钮项。在iOS6下,返回按钮是一个带有标题的边框按钮,其背景图像延伸到整个按钮下方。

在iOS7下,返回控件是一个矢量图像加上前一个屏幕的标题。如果您想使用自定义图像替换默认的矢量图像,则还需要创建自定义蒙版图像。iOS 7使用蒙版使前一个屏幕的标题在导航转换期间从矢量图像中出现或消失。

因为您正在故事板中进行此操作,所以设置外观代理的最佳位置是在应用程序委托的init方法中。

- (instancetype)init
{
    self = [super init];
    if (self)
    {
        //Other UIAppearance proxy calls go here

        // Customizing the Back Bar Buttons
        //ios6 uses whole button background image
        UIImage * btBack_30 = [[UIImage imageNamed:@"btBack_30"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
        UIImage * btBack_24 = [[UIImage imageNamed:@"btBack_24"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 12, 0, 5)];
        [[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_30 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
        [[UIBarButtonItem appearance] setBackButtonBackgroundImage:btBack_24 forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];

        if ([[UIDevice currentDevice].systemVersion integerValue] >= 7)
        {
            //ios7 needs additional chevron replacement image
            UIImage * chevronReplacement = nil;
            chevronReplacement = [chevronReplacement imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
            UIImage * chevronTransitionMaskReplacement = nil;
            chevronTransitionMaskReplacement = [chevronTransitionMaskReplacement imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
            [[UINavigationBar appearance] setBackIndicatorImage:chevronReplacement];
            [[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:chevronTransitionMaskReplacement];
        }    
    }
    return self;
}

你看到问题了吗?“代码在 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 中执行。” - Léo Natan
did和will方法在应用程序加载的不同阶段发生。您的视图在您进行UIAppearance调用之前已经加载完成(这就是为什么第二次看起来正确,但第一次不正确的原因)。 - Fruity Geek
只需留下您回答的第二部分。 - Léo Natan
水果极客,我做出了更改并更新了我的帖子...看起来仍然没有解决问题。我已经在我的应用程序中发布了代码,但我现在也会尝试在我在帖子开头指出的示例中运行它。 - LilMoke
是的,我已经将它移动到init中,但似乎仍然无法工作。顺便说一下,我正在从应用程序委托init方法返回[super init],这样做正确吗? - LilMoke
显示剩余11条评论

0
我尝试在你的演示项目中改变了一些东西,但正如你所说,什么都不起作用。 我以为可能是因为UINavigationController子类的原因,但使用标准的也有相同的行为。
不幸的是,如果你真的必须显示你的按钮,在启动时我会悄无声息地执行打开和关闭的行为...对于这个丑陋的提议我感到抱歉。

你认为这可能是苹果的问题吗?没有任何苹果文档表明我正在使用的调用在iOS 7中无法工作。 - LilMoke
很难说。如果你遇到了同样的问题,可以尝试使用全新的7.1测试版。 - Geraud.ch
测试过了,跟7.1版本的行为一样。 - Cœur

-2

这不是一个错误,而是苹果iOS7的默认行为。

iOS7外观下,您无需设置背景。但是,如果您想要,可以通过将自定义栏按钮添加到自定义导航栏中来实现它。

享受编程吧!


好的,不是很有用,但无论如何,如果这不是个错误,无论是苹果还是我造成的,那么为什么第一次它不能正常工作,第二次按钮出现并可以正确工作呢?您看过上面发布的init方法中的两个示例下载和/或代码了吗? - LilMoke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接