1

I've had this code forever, and it used to work, but it started exhibiting an annoying behavior in, I think, iOS 6. There are several similar threads on SO, but I haven't found a solution that I am comfortable with as of yet. I found a work-around (see below), but it doesn't seem like the "right" way to do it.

Here's a thread with the same issue, but no answers.

When I use an image as a UIBarButtonItem and put it into a UIToolbar, the containing rectangle of the UIToolbar is not entirely transparent. The PNG images that I use for the UIBarButtonItem are transparent, so that's not the issue. Here is an example:

Exhibit 1

When I use text instead of an image as the UIBarButtonItem, transparency is working as expected. As so:

Exhibit 2

Here is the code that has worked since forever:

NSMutableArray *buttonItems = [[[NSMutableArray alloc] initWithCapacity: 1] autorelease];
UIImage *printButtonImage = [UIImage imageNamed:@"buttonPrint.png"];
UIToolbar *toolBarButtons = [[[UIToolbar alloc] initWithFrame:CGRectMake( 0, 0, 52.0, 44.01 )] autorelease];
UIBarButtonItem *printButton = [[[UIBarButtonItem alloc] initWithImage:printButtonImage style:UIBarButtonItemStyleBordered target:self action:@selector(printDocument:)] autorelease];
[buttonItems addObject:printButton];

[toolBarButtons setItems:buttonItems animated:YES];

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:toolBarButtons] autorelease];

According to suggestions from SO, I have added this just prior to [toolBarButtons setItems...] but it has had no affect:

toolBarButtons.backgroundColor = [UIColor clearColor];
[toolBarButtons setTranslucent:YES];
[toolBarButtons setOpaque:NO];

I found this thread, on which the answer by jd provided a work-around (edited to match my variables):

const float colorMask[6] = {222, 255, 222, 255, 222, 255};
UIImage *img = [[UIImage alloc] init];
UIImage *maskedImage = [UIImage imageWithCGImage: CGImageCreateWithMaskingColors(img.CGImage, colorMask)];

[toolBarButtons setBackgroundImage:maskedImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];

I have also seen a couple of solutions to this issue that require getting the color of the top or bottom of the navigationController and applying a gradient, but I refuse to believe that there is no way to make the rectangle of the UIToolbar entirely transparent, which should allow the navigationController color to come through, gradient and all.

Is applying a mask image really the only way to simulate the correct UI experience? Has Apple really not provided a way to simply make a UIToolbar containing rectangle entirely transparent?

For now, I have the correct behavior, but I don't feel like I have the correct solution. This feels like a hack. Does anyone out there know of a better way to accomplish this?

Community
  • 1
  • 1
Chris Ostmo
  • 1,202
  • 1
  • 10
  • 21
  • Sorry - why are you putting a UIToolbar inside a UINavigationBar? Why don't you just add the UIBarButtonItem to the UINavigationBar directly (by way of the `navigationItem`)? – matt May 18 '13 at 00:12
  • @matt Because the features that are accessed by pressing the button(s) are not items that will ever be any part of the navigation stack. The specific example given is that of a print dialog. [UIViewController navigationItem reference](https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html): "...you shouldn’t access this property if you are not using a navigation controller to display the view controller." – Chris Ostmo May 18 '13 at 05:27
  • You can use a navigation bar and a navigation item even if there is no navigation controller. And you can use a navigation controller even if you never intend to navigate, just as an easy way to get a navigation bar. That is all common and legal. What is uncommon, illegal, and unnecessary is what *you* are doing, sticking a toolbar inside a navigation bar. – matt May 18 '13 at 15:53
  • It's interesting that your emphatic conclusion is that my method is incorrect, considering Apple's documentation specifically says to not do what you suggest. I choose to trust Apple's opinion on the matter over yours. – Chris Ostmo May 20 '13 at 19:05
  • I have not suggested anything that Apple says not to do... – matt May 20 '13 at 21:28
  • Please read the link in my first comment to your suggestion. I provided a direct quote in which Apple says that you "shouldn't" do the precise thing you are suggesting. If you feel that the way I am implementing this is uncommon, illegal or unnecessary, please provide documentation from Apple that says so (as I have done to refute your suggestion). There are dozens of threads on SO that use this exact mechanism, so it is definitely NOT uncommon. If it is "illegal," or even not recommended, you should be able to provide links in which Apple supports your claim. If you can't, then you are wrong. – Chris Ostmo May 20 '13 at 21:38
  • No, you've misunderstood what Apple said or what I said. Unfortunately I haven't quite figured out how to break through the mental block for you. Is the problem how to use a UINavigationBar outside a UINavigationController? Or is it populating a UINavigationBar dynamically? My book's section on navigation bars might help: http://www.apeth.com/iOSBook/ch25.html#_uinavigationbar – matt May 20 '13 at 22:21
  • Certainly there was a time when people did some skanky workarounds because a navigation bar could have only one left item and one right item. You may be referring to those. But those days are long gone; now a navigation bar can have many bar button items (several on the left, several on the right, plus the view in the middle). – matt May 20 '13 at 22:27
  • I know how to do precisely what you are proposing, and this code did, in fact, originate in the days when we needed multiple `rightBarButtonItem` elements before it was possible to do so without a `UIToolbar`. However, what I am doing is most definitely NOT uncommon or illegal (or even publicly frowned upon by Apple). You can make an argument that it's unnecessary simply because there's more than one way to do it, but you made a very emphatic statement that this method is wrong, and without Apple's backing on that, that is your opinion and not a fact... – Chris Ostmo May 20 '13 at 22:35
  • ...You have a crap-ton of reputation at SO, and I'll give you respect for that, but this is a long-known and acceptable method for accomplishing this task that many, many people use. My question was simply why transparency isn't working as advertised. The merits of one mechanism to display navigation items vs. another is an unproductive sidetrack. The method I chose and have used in this app since 2008 has been published by Apple as an acceptable means to the end. Stating that the mechanism is "uncommon, illegal and unnecessary" lacks foundation and does not belong in this discussion. – Chris Ostmo May 20 '13 at 22:47
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/30283/discussion-between-chris-ostmo-and-matt) – Chris Ostmo May 20 '13 at 22:56
  • I think you've made it pretty clear there's nothing to discuss. I honestly believe that your difficulties would go away if you used the navigation bar in its basic, intended way, so that my suggestion was not irrelevant but rather was intended as a better approach that removes all the difficulties (because there no toolbar inside the navigation bar to cause those difficulties); but you have a right to disagree, and you do. That's fine. – matt May 21 '13 at 01:23
  • You may be right, but quite honestly, part of my hesitation is that I'm not extremely motivated to devote a lot of time updating this old code at the moment. I'll try your suggestion in a new project. – Chris Ostmo May 22 '13 at 00:57

1 Answers1

3

I do this same thing in my app. (It drives me nuts that we have to host a UIToolbar in a UINavigationBar simply because UINavigationBar doesn't render UIBarButtonItem's in the same way that UIToolbar does.)

Here's what I do to avoid the problem you're seeing:

toolbar.barStyle = self.navigationController.navigationBar.barStyle;
toolbar.tintColor = self.navigationController.navigationBar.tintColor;

The last time I checked, the navigationBar.barStyle is a value that isn't otherwise documented in a UIBarStyle enumeration... I think it was '3'.

Edit: Annoyingly, this doesn't work on iPhone as it works on iPad...

This UIToolbar subclass seems to work fine. Use it instead of a plain UIToolbar:

@interface NavToolbar : UIToolbar
@end

@implementation NavToolbar
- (void) layoutSubviews
{
    [super layoutSubviews];
    self.backgroundColor = [UIColor clearColor];
    self.opaque = NO;
}
- (void) drawRect:(CGRect)rect
{
}
@end

And, here's code to demonstrate using it. Without the NavToolbar I see the issue you've presented; with the NavToolbar it works as desired. Tested in the iOS6 Phone simulator.

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIToolbar* tb = [[NavToolbar alloc] initWithFrame: CGRectMake(0, 0, 44, 44)];

    UIBarButtonItem* bbi = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target: nil action: nil];

    tb.items = @[bbi];
    tb.barStyle = self.navigationController.navigationBar.barStyle;

    UIBarButtonItem* tbbbi = [[UIBarButtonItem alloc] initWithCustomView: tb];
    tbbbi.width = 44;

    self.navigationItem.rightBarButtonItem = tbbbi;
}
TomSwift
  • 39,369
  • 12
  • 121
  • 149
  • I tried the code you provided and get the original results (not entirely transparent). Is it possible that there is another line of code that is necessary to make that work, or maybe something I need to add to the `navigationController`? It seems like a bug to me that `[toolBarButtons setBackgroundColor:[UIColor clearColor]]` isn't the right way to do this. It would seem to suggest that `[UIColor clearColor]` isn't returning "actually clear," but is returning what it thinks is the color beneath the object having its `backgroundColor` set. – Chris Ostmo May 17 '13 at 22:15
  • @Chris Ostmo - darn. I think I have a solution that works on iPhone for you; please see my edited answer. – TomSwift May 17 '13 at 22:41
  • Thanks @TomSwift. My goal in starting the thread was to see if there is a non-hackish solution to this behavior that popped up in iOS 6. Although it is likely to work as a way to get the desired behavior, subclassing `UIToolbar` seems to be a step in the wrong direction as far as having the correct solution. I already found a way to simulate the correct behavior, but I am still hoping to find something that doesn't feel like a hack as the solution. This should just work - the way it did in previous versions of iOS. I'll +1 your answer, but it still isn't the solution for which I am searching. – Chris Ostmo May 20 '13 at 19:13
  • @ChisOstmo - well, I'm not sure it will ever 'just work'. Toolbars have different default bg gradients than navbars. That's what you're seeing. So you need a transparent toolbar or some way to match the navbar gradient. I guess your other option might be to set the toolbar bg image to be a fully transparent png (and set opaque to NO), or possibly to an image of the NavBar gradient. Subclassing to modify behavior isn't really a hack, IMO. At least not in this case. – TomSwift May 20 '13 at 20:02
  • My exact point is that a transparent toolbar "should" provide the desired behavior (and it used to do exactly that), but it doesn't without subclassing. Why does subclassing and setting properties work, but simply setting those same properties directly to the object fail to work? It shouldn't. That seems like a bug. I don't consider your answer to be a "hack" in that it is a poor/accidental implementation, but in the fact that I am looking for a way to make the `UIToolbar` actually transparent by setting properties that are given for that exact purpose. Subclassing should NOT be necessary... – Chris Ostmo May 20 '13 at 20:31
  • ...I tried setting `backgroundColor` and `opaque` directly to the `UIToolbar` to no affect, so subclassing and setting those properties should also have no affect. Am I wrong? I think that the fact that your solution works but setting properties directly does not uncovers a bug in `UIKit`. – Chris Ostmo May 20 '13 at 20:36
  • I've confirmed that your solution works, but subclassing `UIToolbar` to set properties "should" have the same affect as setting those properties directly to a `UIToolbar`. It does not, so I would consider this to be a bug in `UIToolbar` or one of its parents. BTW, setting `tb.barStyle` is not necessary for the results I am expecting. I'll accept your answer since it appears as though setting the intended behavior directly to a `UIToolbar` is broken and your input provided the proof of that fact. I appreciate your effort and time. – Chris Ostmo May 20 '13 at 20:55