5

I have a requirement to set the navigation bar to a custom color and this following code will do that:

[[UINavigationBar appearance]
            setBackgroundImage:navigationBarTileImage forBarMetrics:UIBarMetricsDefault];

However my application invokes the system MFMailComposeViewController and MFMessageComposeViewController and I want the navigation bar to be the default color for those views, so I did this:

[[UINavigationBar appearanceWhenContainedIn: [MyViewControllerBase class], [MyViewController1 class], [MyViewController2 class], nil]
    setBackgroundImage:navigationBarTileImage forBarMetrics:UIBarMetricsDefault];

However now the navigation bar no longer has my default color. Why is appearanceWhenContainedIn not working?

Gruntcakes
  • 37,738
  • 44
  • 184
  • 378

2 Answers2

21

The argument to appearanceWhenContainedIn: represents a view (and/or view controller) containment hierarchy, not a list of possible containers. (Admittedly, the docs aren't clear on this. See the video from WWDC 2011.) Thus,

[UINavigationBar appearanceWhenContainedIn:[NSArray arrayWithObjects:[MyViewControllerBase class], [MyViewController1 class], [MyViewController2 class], nil]]

gives you an appearance proxy for a UINavigationBar contained within a MyViewControllerBase, which in turn is within a MyViewController1 inside a MyViewController2. I'm guessing that's not the containment hierarchy you have.

Instead, look at the view controller which contains your navigation bar. It's probably a generic UINavigationController... but you can't just do

[UINavigationBar apperanceWhenContainedIn:[NSArray arrayWithObject:[UINavigationController class]]]

because then you'll get the mail/message controllers too. And sadly, while you can get at the appearance proxy for a UINavigationBar within a mail/message view controller, there isn't a way to tell it to undo appearance changes made at a more generic level.

It looks like the usual solution to such scenarios is to make yourself a UINavigationController subclass, and use it for the parts of your UI you want skinned. The subclass can be empty -- it only exists to identify parts of your UI for appearanceWhenContainedIn:. (Meanwhile, things like MFMailComposeViewController continue to use the default appearance because they still use the generic UINavigationController.)

rickster
  • 124,678
  • 26
  • 272
  • 326
  • I'll check out that video, looks like there's a relevant one from 2012 too. Would using appearance: and setting it back to the default tint just before I know the MF controllers are going to be pushed, then after they're popped set it back to my tint color work? I'll try an experiment and see, should do I guess. – Gruntcakes Aug 08 '12 at 17:35
  • Will it work? Try it! However, it's an evasion of the normal API pattern -- even if it works now, Apple only promises that something will continue to work in future OS releases if you do it the "right" way. – rickster Aug 13 '12 at 05:17
  • This is incredible. The docs aren't just unclear, they say: "Returns the appearance proxies when the object is contained by one of the specified classes", which is plainly wrong. I wasted too many hours today debugging this. This solution works for me, thanks for clearing this up. – Theo Apr 17 '18 at 10:59
0

Indeed, like @rickster said, the appearanceWhenContainedIn: method customizes the appearances for instances of a class contained WITHIN an instance of a container class, or instances in a hierarchy.

Not in every case you have a set of contained class you want to customize, but different containers. The solution to be able to customize several components is simply by creating an array of the classes you need to customize, and iterate! Like so:

NSArray *navigationClass = [NSArray arrayWithObjects:[BSNavigationController class], [DZFormNavigationController class], nil];

for (Class class in navigationClass)
{
    //// Customize all the UINavigationBar background image tilling
    [[UINavigationBar appearanceWhenContainedIn:class, nil] setBackgroundImage:[UIImage imageNamed:@"yourImage"] forBarMetrics:UIBarMetricsDefault];
    [[UINavigationBar appearanceWhenContainedIn:class, nil] setTintColor:[UIColor blackColor]];

    // Title Text Attributes
    NSDictionary *titleAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [UIColor whiteColor], UITextAttributeTextColor,
                                     [UIColor darkGrayColor], UITextAttributeTextShadowColor,
                                     [UIFont boldSystemFontOfSize:20.0], UITextAttributeFont,
                                     [NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,nil];

    //// Customize all the UINavigationBar title attributes
    [[UINavigationBar appearanceWhenContainedIn:class, nil] setTitleTextAttributes:titleAttributes];
}
DZenBot
  • 4,806
  • 2
  • 25
  • 27