UINavigationBar and custom backgrounds

99 views
Skip to first unread message

Brent Simmons

unread,
Sep 4, 2010, 5:04:12 PM9/4/10
to cocoa-...@googlegroups.com
This question has been asked many times, but I've never seen a definitive answer. (Correct me if I'm wrong.)

Here's the situation: UINavigationController with UINavigationBar -- and no nib. Created in code.

I want to layer some drawing on top of all or part of the standard UINavigationBar: the normal drawing should happen underneath what I want to draw. But I can't use a subclass, since there's no nib.

There are two approaches that I know of -- but I'm hoping there's a third way that doesn't suck.

1. Get into the view hierarchy and do things. Ugh. (Hard to get right, I've found, and it feels icky, since it's a private view hierarchy.)

2. Swizzle methods (drawRect and my own drawing method). That feels even worse. But it works perfectly and uses much less code.

Please don't say, "Hey, Brent, use a nib already, wouldja?" -- I have an excellent reason for not using a nib (otherwise I'd be happy to).

Third way? It exists?

-Brent

PS We're up to 61 members now.

Justin Spahr-Summers

unread,
Sep 4, 2010, 5:29:29 PM9/4/10
to cocoa-...@googlegroups.com
2010/9/4 Brent Simmons <br...@ranchero.com>:

What kind of drawing are you hoping to do? If it's just additional
views, there shouldn't be a problem adding a subview to a navigation
bar. If it's rendering in the wrong order, you can always adjust the
zPosition of the subview's layer.

Luke Redpath

unread,
Sep 4, 2010, 5:36:27 PM9/4/10
to cocoa-unbound
Brent, I have not done this myself but I believe the third way is a
category on UINavigationBar.

Ive always suspected that his is how e textured bar in Reeder for both
iPhone and iPad is implemented, my evidence for this being that the
effect appears on all navigation bars, including those of system
provided views, like the MP* media player controllers.

If is is how Silvio has done it, then the above could be perceived as
a possible drawback. But maybe not.

Maybe Silvio will come along and share his secret :)

Brent Simmons

unread,
Sep 4, 2010, 5:48:55 PM9/4/10
to cocoa-...@googlegroups.com

Drawing an image, usually.

I've found that this approach is easier said than done -- given the presence of other buttons and views, and given that we're hacking into a private view hierarchy we don't own, it didn't always work as expected. Often, for instance, a back button would get drawn under my image view after navigating.

Even if it worked, it's wrong -- as is method-swizzling. Which is why I was hoping someone knew of a third, non-icky way of doing this.

-Brent

Brent Simmons

unread,
Sep 4, 2010, 5:53:46 PM9/4/10
to cocoa-unbound
On Sep 4, 2:36 pm, Luke Redpath <l...@lukeredpath.co.uk> wrote:
> Brent, I have not done this myself but I believe the third way is a
> category on UINavigationBar.
>
> Ive always suspected that his is how e textured bar in Reeder for both
> iPhone and iPad is implemented, my evidence for this being that the
> effect appears on all navigation bars, including those of system
> provided views, like the MP* media player controllers.

I believe I've gotten it work with a category, sort-of -- except I can
only draw the entire navigation bar. Often I want to draw on top of
the standard navigation bar drawing, not over the entire thing.

-Brent

Michael Tyson

unread,
Sep 4, 2010, 5:54:25 PM9/4/10
to cocoa-...@googlegroups.com
Third option: Submit a bug report to Apple asking for easier subclassing? =)

Swizzling is how I've recently done it; is it really that bad an option? It seems rather tidy to me, in the absence of any hooks provided by Apple. I used the tintColor to determine whether to apply my modification to the navigation bar, so that I had the option of using system-standard bars too, instead of the effect being applied to all bars.

I just wanted a navigation bar without a background, so I just check for [[self tintColor] isEqual:[UIColor clearColor]].

Cheers =)
Michael

Justin Spahr-Summers

unread,
Sep 4, 2010, 5:55:21 PM9/4/10
to cocoa-unbound
Oh, sorry, a background. Skipped over the subject, and saw your note
about the normal drawing needing to happen underneath.

If you really want a custom background, the best way is probably to
create an opaque image that has the appearance you want, and then set
the contents of the navigation bar's layer:

UIImage *background = [UIImage imageNamed:@"navbar-background.png"];
navigationBar.layer.contents = (id)background.CGImage;

This is the least hacky way I know of doing it, and it's also probably
got the best performance out of all the options.

Michael Tyson

unread,
Sep 4, 2010, 5:57:10 PM9/4/10
to cocoa-...@googlegroups.com
On 4 Sep 2010, at 22:53, Brent Simmons wrote:

> I believe I've gotten it work with a category, sort-of -- except I can
> only draw the entire navigation bar. Often I want to draw on top of
> the standard navigation bar drawing, not over the entire thing.

Oh, can't you just call the original (swizzled) drawRect method to do the base rendering? Or have I misunderstood?

Michael Ash

unread,
Sep 4, 2010, 8:07:55 PM9/4/10
to cocoa-...@googlegroups.com
On Sep 4, 2010, at 5:04 PM, Brent Simmons wrote:

> 2. Swizzle methods (drawRect and my own drawing method). That feels even worse. But it works perfectly and uses much less code.
>
> Please don't say, "Hey, Brent, use a nib already, wouldja?" -- I have an excellent reason for not using a nib (otherwise I'd be happy to).
>
> Third way? It exists?

You can achieve nib-like class substitution by archiving and unarchiving. Roughly:

1) Subclass UINavigationBar and override whatever you need.
2) Create your UINavigationController.
3) Serialize it using NSKeyedArchiver.
4) Create an NSKeyedUnarchiver and use -setClass:forClassName: to point it at your UINavigationBar subclass.
5) Use that unarchiver to deserialize the UINavigationController.
6) There is no step six!

Mike

Sebastian Celis

unread,
Sep 4, 2010, 10:53:33 PM9/4/10
to cocoa-unbound
On Sep 4, 4:04 pm, Brent Simmons <br...@ranchero.com> wrote:
> There are two approaches that I know of -- but I'm hoping there's a third way that doesn't suck.
>
> 1. Get into the view hierarchy and do things. Ugh. (Hard to get right, I've found, and it feels icky, since it's a private view hierarchy.)
>
> 2. Swizzle methods (drawRect and my own drawing method). That feels even worse. But it works perfectly and uses much less code.

Hi Brent. I actually use a combination of (1) and (2) which I think
works quite well. It does not use drawRect:, which I agree is icky. It
uses method swizzling to ensure the background image view is always at
the back of the view hierarchy. I even wrote up a blog post about it
containing sample code.

http://sebastiancelis.com/2009/dec/21/adding-background-image-uinavigationbar/
http://github.com/scelis/ExampleNavBarBackground

Cheers,

Sebastian

Brent Simmons

unread,
Sep 4, 2010, 11:17:01 PM9/4/10
to cocoa-...@googlegroups.com

Oh *hell* yes. Thanks, Mike. Just what I wanted.

-Brent

Dave DeLong

unread,
Sep 4, 2010, 11:25:06 PM9/4/10
to cocoa-...@googlegroups.com
That's just about the awesomest thing I've read recently.

Dave

Michael Ash

unread,
Sep 5, 2010, 12:47:56 AM9/5/10
to cocoa-...@googlegroups.com
On Sep 4, 2010, at 11:17 PM, Brent Simmons wrote:

> Oh *hell* yes. Thanks, Mike. Just what I wanted.

Glad to be of service. I had to pay you back for setting up this list. :)

Mike

Michael Tyson

unread,
Sep 5, 2010, 7:01:24 AM9/5/10
to cocoa-...@googlegroups.com
Damn. That's freaking awesome.

st3fan

unread,
Sep 5, 2010, 10:25:05 AM9/5/10
to cocoa-unbound
On Sep 4, 5:04 pm, Brent Simmons <br...@ranchero.com> wrote:
> This question has been asked many times, but I've never seen a definitive answer. (Correct me if I'm wrong.)
>
> Here's the situation: UINavigationController with UINavigationBar -- and no nib. Created in code.
>
> I want to layer some drawing on top of all or part of the standard UINavigationBar: the normal drawing should happen underneath what I want to draw. But I can't use a subclass, since there's no nib.
>
> There are two approaches that I know of -- but I'm hoping there's a third way that doesn't suck.
>
> 1. Get into the view hierarchy and do things. Ugh. (Hard to get right, I've found, and it feels icky, since it's a private view hierarchy.)
>
> 2. Swizzle methods (drawRect and my own drawing method). That feels even worse. But it works perfectly and uses much less code.

I've been using:

@implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect
{
UIImage *image = [UIImage imageNamed: @"Background.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width,
self.frame.size.height+5)];
}
@end

To get a custom background in a UINavigationBar. If you add a call to
super there, then the original drawing will also happen.

S.

st3fan

unread,
Sep 5, 2010, 11:11:29 AM9/5/10
to cocoa-unbound
On Sep 4, 5:04 pm, Brent Simmons <br...@ranchero.com> wrote:
> This question has been asked many times, but I've never seen a definitive answer. (Correct me if I'm wrong.)
>
> Here's the situation: UINavigationController with UINavigationBar -- and no nib. Created in code.
>
> I want to layer some drawing on top of all or part of the standard UINavigationBar: the normal drawing should happen underneath what I want to draw. But I can't use a subclass, since there's no nib.
>
> There are two approaches that I know of -- but I'm hoping there's a third way that doesn't suck.
>
> 1. Get into the view hierarchy and do things. Ugh. (Hard to get right, I've found, and it feels icky, since it's a private view hierarchy.)
>
> 2. Swizzle methods (drawRect and my own drawing method). That feels even worse. But it works perfectly and uses much less code.

StuFF mc (.com)

unread,
Sep 5, 2010, 12:00:08 PM9/5/10
to cocoa-unbound
I use method 2, works fine - if there's another Method I'd love to
hear it, and if there's anything wrong with method 2, I'd also to hear
it, but it really works perfectly :-)


@implementation UINavigationBar (UINavigationBarCategory)

- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed: @"navbar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width,
self.frame.size.height)];
}

@end

Daniel Jalkut

unread,
Sep 5, 2010, 1:39:51 PM9/5/10
to cocoa-...@googlegroups.com
Hi Stefan -

On Sep 5, 2010, at 10:25am, st3fan wrote:

> I've been using:
>
> @implementation UINavigationBar (CustomImage)
> - (void)drawRect:(CGRect)rect
> {
> UIImage *image = [UIImage imageNamed: @"Background.png"];
> [image drawInRect:CGRectMake(0, 0, self.frame.size.width,
> self.frame.size.height+5)];
> }
> @end
>
> To get a custom background in a UINavigationBar. If you add a call to
> super there, then the original drawing will also happen.

If I'm interpreting your technique correctly, you're relying on undocumented and unreliable behavior of category methods in Objective-C.

Since you are not subclassing UINavigationBar, you can't safely override its drawRect: method without swizzling its implementation out. Your category implementation is being reached probably only because it happens to get loaded after the standard UINavigationBar method.

I'm not sure why [super drawRect:] works for you, if it does. It doesn't seem likely to me that UINavigationBar's own drawRect: would ever get reached. When you call [super drawRect:] from your category method on UINavigationBar, you are actually calling through to UIView's drawRect.

Perhaps what's happening here is UINavigationBar draws very little of its own content, and when you draw through to [UIView drawRect:], it probably causes all the subviews to get drawRect: messages, which subtantially makes it look like the superclass drawRect is being called.

I would not rely on this kind of approach personally, because any change in Apple's implementation of Objective-C class loading could cause the technique to stop working, and that would be especially embarrassing for apps that are already in the App Store.

Daniel

Justin Spahr-Summers

unread,
Sep 5, 2010, 1:46:28 PM9/5/10
to cocoa-...@googlegroups.com
2010/9/5 Daniel Jalkut <jal...@gmail.com>:

I agree that [super drawRect:] probably does *not* work as intended in
that example, and could very easily break in a future SDK update;
however, using categories to override existing methods is a well-known
and well-defined feature of Objective-C. The real uncertainty would be
if another category on UINavigationBar existed (hypothetically) that
defined the same method, in which case load order is indeterminate.

Daniel Jalkut

unread,
Sep 5, 2010, 1:59:18 PM9/5/10
to cocoa-...@googlegroups.com

On Sep 5, 2010, at 1:46pm, Justin Spahr-Summers wrote:

> I agree that [super drawRect:] probably does *not* work as intended in
> that example, and could very easily break in a future SDK update;
> however, using categories to override existing methods is a well-known
> and well-defined feature of Objective-C. The real uncertainty would be
> if another category on UINavigationBar existed (hypothetically) that
> defined the same method, in which case load order is indeterminate.

Thanks, Justin for the clarification. I had to look it up in the documentation to appreciate that, in fact, you can reliably use a category method to completely override a non-category method.

For others who want to review the documentation on this point, just search on "strongly discouraged" :)

http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocCategories.html

The fact that so much thinking is required in order to do it without getting burned, is one reason I would always prefer a subclassing technique.

Daniel

Justin Spahr-Summers

unread,
Sep 5, 2010, 2:02:52 PM9/5/10
to cocoa-...@googlegroups.com
2010/9/5 Daniel Jalkut <jal...@gmail.com>:

Granted, it's a terrible thing to do. But even terrible things are
useful sometimes!

--
Justin Spahr-Summers

Michael Ash

unread,
Sep 5, 2010, 2:03:46 PM9/5/10
to cocoa-...@googlegroups.com

Another reason is that there is no guarantee that the original method you're trying to override isn't itself implemented in a category. It's legitimate to use categories to simply split up the implementation of a class for design purposes, and Apple may well do this (or start doing it later).

Mike

Justin Spahr-Summers

unread,
Sep 5, 2010, 2:06:56 PM9/5/10
to cocoa-...@googlegroups.com
2010/9/5 Michael Ash <micha...@gmail.com>:

Fair to say, though the same issue exists with method swizzling at the
time of +load or +initialize, which I've seen a fair few examples of.

--
Justin Spahr-Summers

Jim Dovey

unread,
Sep 5, 2010, 2:16:47 PM9/5/10
to cocoa-...@googlegroups.com
On 2010-09-05, at 1:59 PM, Daniel Jalkut wrote:

> On Sep 5, 2010, at 1:46pm, Justin Spahr-Summers wrote:
>
>> I agree that [super drawRect:] probably does *not* work as intended in
>> that example, and could very easily break in a future SDK update;
>> however, using categories to override existing methods is a well-known
>> and well-defined feature of Objective-C. The real uncertainty would be
>> if another category on UINavigationBar existed (hypothetically) that
>> defined the same method, in which case load order is indeterminate.
>

> The fact that so much thinking is required in order to do it without getting burned, is one reason I would always prefer a subclassing technique.

Hehe, naughty st3fan— I'd have thought listening to me rant on this sort of subject at Kobo for the last few months would have tempered this behaviour by now ;o)

I'd ALWAYS go for the subclassing route if possible, or if difficult I would use swizzling, which the new ObjC runtime API makes very lovely and easily, through things like this (thanks to my Apress co-author Mike Ash for the snippet I'm basing this on) —

void MethodSwizzle( Class c, SEL origSEL, SEL newSEL )
{
Method origMethod = class_getInstanceMethod( c, origSEL );
Method newMethod = class_getInstanceMethod( c, newSEL );

// first try adding the new method
// if it worked, then it's an override of the superclass
if( class_addMethod(c, origSEL,
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod)) )
{
// bring the superclass method down into this class
// under the new selector, so it can get called that way
// note that we copy the original method using the new name,
// purely so we can call it from our override
class_replaceMethod( c, newSEL,
method_getImplementation(origMethod),
method_getTypeEncoding(origMethod) );
}
else
{
// it failed, so the method is implemented in this class
// exchange the two and we're done
method_exchangeImplementations( origMethod, newMethod );
}
}

@implementation UINavigationBar (my_swizzled_drawing_stuff)
- (void)my_overridden_drawRect:(CGRect)rect
{
[self my_overridden_drawRect:rect]; // now calls the original function, since selectors were swapped


UIImage *image = [UIImage imageNamed: @"Background.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height+5)];
}
@end

int main(void)
{
// …

MethodSwizzle( [UINavigationController class], @selector(drawRect:), @selector(my_overridden_drawRect:) );

// …
}

Also, since Mike's own reply just came in, mentioning the use of categories which aren't listed in official Apple header files, I can verify that there are a good number of categories we don't know about, especially in some of the UIView-type classes. I can't name any right now (not at my work computer to check) but within the last couple of days I noticed something in my class-dumped headers that made me go 'ooh, it's in a category is it?'

Cheers,
-Jim

Ken Ferry

unread,
Sep 5, 2010, 2:51:49 PM9/5/10
to cocoa-...@googlegroups.com
I think this is actually more likely to cause problems than traversing the private view hierarchy and swizzling the isa of any UINavigationBar you encounter.  That is, you should not feel clean about doing this. :-)

The really good thing about a nib as compared to just archiving something is that it forms a clear segmentation between what is and what is not supposed to be replicated.  With bindings on the desktop, for example, there are some bindings where the thing you're binding to should be part of the archive, and some where it's a global thing where each unarchived copy should hook back up to the same global thing.  While archives do have scope for handling that kind of thing, just archiving a single root object doesn't cut it, and I don't think there's any way you can know what the root objects need to be here, is there? The Cocoa frameworks have a few places where they attempt to duplicate view hierarchies using archiving, and it causes nothing but problems.  There are fewer than there used to be.  

Basically, if you archive and unarchive the tree, there's a larger surface area for possible problems than a more targeted change would have.

-Ken

Ken Ferry

unread,
Sep 5, 2010, 2:54:53 PM9/5/10
to cocoa-...@googlegroups.com
There shouldn't be much need to swizzle a +load.  Every class and category get their own +load method invoked.  +load is magic.

-Ken
 

--
Justin Spahr-Summers

Justin Spahr-Summers

unread,
Sep 5, 2010, 3:03:39 PM9/5/10
to cocoa-...@googlegroups.com
2010/9/5 Ken Ferry <kenf...@gmail.com>:

Right, not swizzling +load, but swizzling IN +load. If you do that
within a category, for instance, the order in which that category
loads is indeterminate, so the same double-implementation problem
exists. I'd also wager that it could still conflict with methods
defined in categories, since the runtime almost certainly uses the
equivalent of class_replaceMethod() under the hood.

--
Justin Spahr-Summers

Stefan Arentz

unread,
Sep 5, 2010, 3:18:38 PM9/5/10
to cocoa-...@googlegroups.com

Yeah I don't know where that remark about super was coming from. Ignore it.

I've been using the above category since 2.2.1 or so and it works fine in practice.

I agree that there is a potential problem with another category doing the same thing, but I am pretty sure that same uncertainty is there with method swizzling. You simply can't know for sure.

I just prefer this because it is more concise.

S.

Stefan Arentz

unread,
Sep 5, 2010, 3:19:32 PM9/5/10
to cocoa-...@googlegroups.com

On 2010-09-05, at 2:16 PM, Jim Dovey wrote:

> On 2010-09-05, at 1:59 PM, Daniel Jalkut wrote:
>
>> On Sep 5, 2010, at 1:46pm, Justin Spahr-Summers wrote:
>>
>>> I agree that [super drawRect:] probably does *not* work as intended in
>>> that example, and could very easily break in a future SDK update;
>>> however, using categories to override existing methods is a well-known
>>> and well-defined feature of Objective-C. The real uncertainty would be
>>> if another category on UINavigationBar existed (hypothetically) that
>>> defined the same method, in which case load order is indeterminate.
>>
>> The fact that so much thinking is required in order to do it without getting burned, is one reason I would always prefer a subclassing technique.
>
> Hehe, naughty st3fan— I'd have thought listening to me rant on this sort of subject at Kobo for the last few months would have tempered this behaviour by now ;o)

No, I must have been working at home that day.

S.

Guy English

unread,
Sep 5, 2010, 3:31:14 PM9/5/10
to cocoa-...@googlegroups.com
Brent says:

> I want to layer some drawing on top of all or part of the standard UINavigationBar: the normal drawing should happen underneath what I want to draw. But I can't use a subclass, since there's no nib.

I like fancy pants stuff as much as the next guy but I think there’s a simpler route here.

Consider simply creating a CALayer with the contents you want and adding it as a sublayer of the navigation bar’s view. If you’re concerned with keeping it on top of anything else that is added give it a suitably high zPosition. You’re not even really messing with the view hierarchy in anyway that isn’t supported by the APIs. Method swizzling -drawRect: on UINavigationBar is less helpful I think and Mike’s archive / unarchive trick sounds like it may end up problematic (though probably works like a charm now).

UINavigationBar is backed by a CALayer. This CALayer has it’s delegate set to the UINavigationBar itself. I just tried making some other object the layer delegate and calling back into the UINavigationBar but this doesn’t work because UIKit is assuming it’s talking to a view for that layer. Specifically it dies trying to call _invalidateSubviewCache which seems a little unfair. So that potential solution is out the window.

I think just adding a layer is the simplest, easiest to debug and least surprising way to get what you want done. The method swizzle way takes out the drawRect across the whole app, which I’m pretty sure isn’t something Brent wants. Ken’s suggestion of running the view hierarchy and messing with the isa pointer of UINavigationBars is good but if you add an ivar to your subclass you’ll find yourself with some really annoying bugs to track down. I’d really stick with simple until it’s proven not to work.

- Guy

Michael Ash

unread,
Sep 5, 2010, 3:45:28 PM9/5/10
to cocoa-...@googlegroups.com
On Sep 5, 2010, at 2:51 PM, Ken Ferry wrote:

> I think this is actually more likely to cause problems than traversing the private view hierarchy and swizzling the isa of any UINavigationBar you encounter. That is, you should not feel clean about doing this. :-)

That's OK, I rarely feel clean when coding these days.

> The really good thing about a nib as compared to just archiving something is that it forms a clear segmentation between what is and what is not supposed to be replicated. With bindings on the desktop, for example, there are some bindings where the thing you're binding to should be part of the archive, and some where it's a global thing where each unarchived copy should hook back up to the same global thing. While archives do have scope for handling that kind of thing, just archiving a single root object doesn't cut it, and I don't think there's any way you can know what the root objects need to be here, is there? The Cocoa frameworks have a few places where they attempt to duplicate view hierarchies using archiving, and it causes nothing but problems. There are fewer than there used to be.
>
> Basically, if you archive and unarchive the tree, there's a larger surface area for possible problems than a more targeted change would have.

I completely understand what you're getting at in terms of the problems with archiving/unarchiving. This is one of the things I really hated about NSCollectionView in 10.5, and caused no end to problems there.

However, does it really apply in this particular situation? What I propose is to alloc/init the view and then immediately archive/unarchive it without doing anything else to it. It seems to me that, since it hasn't been configured, hasn't been added to any other views, and in general no pointers have been established between this object and the outside world, it should withstand the archive/unarchive process without any trouble.

If you were trying to do this on the fly to modify an existing object that was already in use, I'd think that would really not be a good idea at all. But this particular scenario doesn't seem *too* bad....

Mike

Matt Drance

unread,
Sep 5, 2010, 3:49:13 PM9/5/10
to cocoa-...@googlegroups.com
Indeed, from the UIView.layer reference:

Warning: Since the view is the layer’s delegate, you should never set the view as a delegate of another CALayer object. Additionally, you should never change the delegate of this layer.

I'm not sure inserting a layer is any less of a gamble than inserting a view. There is a lot of opaque behavior to UINavigationBar / Item. Note that we add UIBarButtonItems, which are NSObjects, to the bar, which implies what actually happens after that is an implementation detail subject to change. Once you start messing with z order you could easily clobber your nav items which you cannot (legally) access or manipulate.

Jim Dovey

unread,
Sep 5, 2010, 3:49:37 PM9/5/10
to cocoa-...@googlegroups.com

If you're only wanting to change the background, and that background is usually put in place by UINavigationBar's -drawRect: method, then I'd just add a new subview at index 0, and I'd draw the background with that— normally I'd use a UIImageView with a tiled or stretched background image. The various buttons and suchlike are all views themselves, so you only need to add the subview under the others. I've actually done that before with decent effects on a UIToolbar for the iPad (although for other reasons we ultimately switched to using a completely custom toolbar class).

-Jim

Guy English

unread,
Sep 5, 2010, 3:54:14 PM9/5/10
to cocoa-...@googlegroups.com
> Indeed, from the UIView.layer reference:

Heh. Mom told me I should learn to reed.

> I'm not sure inserting a layer is any less of a gamble than inserting a view.

Agreed — probably close to being the same thing modulo interacting with responder chain / subview arrays.

> There is a lot of opaque behavior to UINavigationBar / Item. Note that we add UIBarButtonItems, which are NSObjects, to the bar, which implies what actually happens after that is an implementation detail subject to change. Once you start messing with z order you could easily clobber your nav items which you cannot (legally) access or manipulate.

True. I meant give the new layer a zPosition of 1,000 or something to try to keep it atop everything else but, yeah, that could be problematic too.

Keeping the objective in mind perhaps simply adding a new sibling view of the NavigationBar and making sure it stays on top would avoid messing with UINavigationBar internals while keeping the same visual effect. (But you’d need to manage the new view to keep it in sync with where the NavBar is)

- Guy

Michael Tyson

unread,
Sep 5, 2010, 4:34:26 PM9/5/10
to cocoa-...@googlegroups.com
On 5 Sep 2010, at 20:49, Jim Dovey wrote:

> If you're only wanting to change the background, and that background is usually put in place by UINavigationBar's -drawRect: method, then I'd just add a new subview at index 0, and I'd draw the background with that— normally I'd use a UIImageView with a tiled or stretched background image. The various buttons and suchlike are all views themselves, so you only need to add the subview under the others. I've actually done that before with decent effects on a UIToolbar for the iPad (although for other reasons we ultimately switched to using a completely custom toolbar class).


Perhaps I went wrong somewhere when I gave this a try - actually, from memory I did this with a layer, not a view - but I had problems with various interface elements moving underneath the background layer when pushing/popping view controllers. For a while I tried using a category to override +layer and providing a custom layer class that disallows adding anything at index 0, thereby keeping the background in the right position, but this felt insane pretty quickly. I ended up swizzling too. Didn't try Guy's zPosition thing though - sounds interesting.

Ken Ferry

unread,
Sep 5, 2010, 6:48:25 PM9/5/10
to cocoa-...@googlegroups.com
That might be the case here.  I haven't worked with UINavigationBar, and I don't know how complex it is or is likely to be.

Mostly, I didn't want other folk to think that lack of explicitly verboten interfaces == kosher in this case.  It's still a hack.

-Ken
 

Mike

Jon Hjelle

unread,
Sep 5, 2010, 9:14:57 PM9/5/10
to cocoa-...@googlegroups.com
I want to layer some drawing on top of all or part of the standard UINavigationBar: the normal drawing should happen underneath what I want to draw. But I can't use a subclass, since there's no nib.
 

I'm biting the bullet and admitting my ignorance. Why is it that a subclass won't work in this case without a nib? Is it something specific to UINavigationControllers/UINavigationBars?

I might as well start learning. :-)

Jon 

Matt Drance

unread,
Sep 5, 2010, 9:51:00 PM9/5/10
to cocoa-...@googlegroups.com
The navigationBar property on UINavigationController is read only. You can't set it. Subclassing UINavigationController to override -navigationBar to return a custom subclass is unreliable because you don't know if UINavigationController accesses the ivar prior to anyone calling the getter you overrode. I'm pretty sure I went down this road in the 2.x days and died a swift death.

Matt Drance
Bookhouse Software, LLC


Duncan Campbell

unread,
Sep 5, 2010, 10:02:47 PM9/5/10
to cocoa-...@googlegroups.com
You might want to take a look at this sample project by TIm Davies:



On 06/09/2010, at 11:14 AM, Jon Hjelle wrote:

I want to layer some drawing on top of all or part of the standard UINavigationBar: the normal drawing should happen underneath what I want to draw. But I can't use a subclass, since there's no nib.

---------------------------------------------------------------
Duncan Campbell
http://objective-d.com
Twitter: dunk
---------------------------------------------------------------

Official Project Stages:
1. Uncritical Acceptance
2. Wild Enthusiasm
3. Dejected Disillusionment
4. Total Confusion
5. Search for the Guilty
6. Punishment of the Innocent
7. Promotion of the Non-participants

Jon Hjelle

unread,
Sep 5, 2010, 10:28:52 PM9/5/10
to cocoa-...@googlegroups.com
On Sun, Sep 5, 2010 at 8:51 PM, Matt Drance <ma...@bookhousesoftware.com> wrote:
The navigationBar property on UINavigationController is read only. You can't set it. Subclassing UINavigationController to override -navigationBar to return a custom subclass is unreliable because you don't know if UINavigationController accesses the ivar prior to anyone calling the getter you overrode. I'm pretty sure I went down this road in the 2.x days and died a swift death.

<slaps forehead> Thanks, Matt.

My apologies for not checking the docs first (especially to you Mike, as I'm pretty sure that violates your "How to Ask a Question" guidelines).

Jon

Jim Dovey

unread,
Sep 5, 2010, 10:31:43 PM9/5/10
to cocoa-...@googlegroups.com
Another possible approach is to use a pattern color as a custom bar tint. Anyone tried that before who can say if it works?

Sent from my iPhone

Matt Drance

unread,
Sep 5, 2010, 10:38:16 PM9/5/10
to cocoa-...@googlegroups.com
That was also unsuccessful when I tried long ago; I believe the entire navbar went white. Might be worth another try these days though.

Jeff LaMarche

unread,
Sep 5, 2010, 11:08:46 PM9/5/10
to cocoa-...@googlegroups.com
I can't say that I've tried every possibility when it comes to subclassing UINavigationController, but I've tried a great many, and I have come to the conclusion that you're better off creating a workalike subclass of UIViewController than getting anywhere near a subclass of that monstrosity. I've created custom navigation controllers now for three separate projects, and to be perfectly honest, what UINavigationController does just isn't rocket science. I've been toying with the idea of creating a generic replacement for UINavigationController that can be subclassed, taking the three workalikes I've written to date and generalizing them out into a single generic class, but haven't found the right inspiration (or, for that matter, the time) to do so yet.

When I get back from vacation, I'll see if I can't post one of my workalike classes. Unfortunately, they were all done for contract clients and so I can't share them without permission, but I might be able to get permission. I'll look into it later in the week when I'm back in my office - but no promises.

In the meantime, for the intrepid of you who want to create your own workalike, here's a couple of category methods on UIView to get you started. These methods simulate the animation that UINavigationController does when it pushes or pops a view controller. These are called on the view being animated on or off, not on the navigation controller:

@implementation UIView(MCAnimations)
- (void)simulateNavControllerAnimateOn
{
    self.alpha = 0.0f;
    [UIView beginAnimations:@"Nav Bar Simulate On" context:nil];
    [UIView setAnimationDuration:0.5f];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    self.alpha = 1.0f;
    [UIView commitAnimations];
}
- (void)simulateNavControllerAnimateOff
{
    [UIView beginAnimations:@"Nav Bar Simulate Off" context:nil];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    self.transform = CGAffineTransformTranslate(self.transform, +[UIScreen mainScreen].applicationFrame.size.width, 0.f);
    [UIView commitAnimations];
    [self performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:0.6];
}
@end


Jeff

Matt Drance

unread,
Sep 5, 2010, 11:41:00 PM9/5/10
to cocoa-...@googlegroups.com
Huh, you know I never tried that.

On Sep 5, 2010, at 8:13 PM, Michael Ash wrote:

> On Sep 5, 2010, at 9:51 PM, Matt Drance wrote:
>
>> The navigationBar property on UINavigationController is read only. You can't set it. Subclassing UINavigationController to override -navigationBar to return a custom subclass is unreliable because you don't know if UINavigationController accesses the ivar prior to anyone calling the getter you overrode. I'm pretty sure I went down this road in the 2.x days and died a swift death
>

> I didn't realize the thing was publicly accessible. In that case, another alternative to swizzling/archive hackery would be to subclass, being careful not to add any ivars in the subclass, and then use object_setClass() at some conveniently early point. Hacky, but at least you're not modifying *every* instance in your app....
>
> Mike

Michael Ash

unread,
Sep 5, 2010, 11:13:40 PM9/5/10
to cocoa-...@googlegroups.com

Guy English

unread,
Sep 6, 2010, 12:03:47 AM9/6/10
to cocoa-...@googlegroups.com
> On Sep 5, 2010, at 8:13 PM, Michael Ash wrote:
>> another alternative to swizzling/archive hackery would be to subclass, being careful not to add any ivars in the subclass, and then use object_setClass() at some conveniently early point.
> Huh, you know I never tried that.

Ken mentioned this technique offhand. It works but, really and as Mike says, if you add ivars you’ll be in serious pain. I did this kind of thing a few times when wanting to change an NSCell to a subclass after loading a Nib without having to jump through all kinds of hoops. I ended up writing a method on, I believe, NSObject - something like transmuteToClass:. It’d check that the instance size of the target object and the new class were identical before doing anything and it’d flip out and yell at you if they weren’t. I’d paste the code here but I don’t think I’ve got it anymore, it’d be in the Rogue Amoeba repository somewhere.

The reason you want to check the size of the two classes first is, I promise you, someone at some point is going to come along and want to add an ivar to your subclass. Since it’s a subclass that controls a visual effect it’ll be all the more tempting to add some UIImage or UIColor or whatever. Once that happens you won’t crash. Things will probably work a little bit. If the instance size of your UINavigationBar subclass isn’t aligned nicely and there’s space at the end of the allocations for an extra pointer or two you may never even notice — but eventually you’ll end up writing over some other object when you write to your ivar and you’ll turn to drink and go bald before you figure out what’s happening. So be careful.

That said, hey, welcome to Cocoa-Unbound — you can totally mess with the isa pointer of an instance to do all kinds of fun and crazy stuff.

- Guy

Michael Ash

unread,
Sep 6, 2010, 1:22:56 PM9/6/10
to cocoa-...@googlegroups.com
On Sep 6, 2010, at 12:03 AM, Guy English wrote:

> I’d paste the code here but I don’t think I’ve got it anymore, it’d be in the Rogue Amoeba repository somewhere.

It is. It also assumes the old runtime with direct struct access, so not too useful these days. However, it really isn't complicated. It just checks to make sure it's really a subclass, checks that the instance sizes are equal, and then assigns to the isa. Would be trivial to rewrite for the modern runtime API using class_getInstanceSize and object_setClass.

> That said, hey, welcome to Cocoa-Unbound — you can totally mess with the isa pointer of an instance to do all kinds of fun and crazy stuff.

Indeed. I should probably point out now, in case anyone isn't familiar with all the crazy stuff I do on my blog, that if I describe a technique, you should assume it's insane and dangerous unless I say otherwise.

Mike

Jon Olson

unread,
Sep 6, 2010, 4:45:46 PM9/6/10
to cocoa-...@googlegroups.com

On Sep 6, 2010, at 1:22 PM, Michael Ash wrote:

> On Sep 6, 2010, at 12:03 AM, Guy English wrote:
>
>> I’d paste the code here but I don’t think I’ve got it anymore, it’d be in the Rogue Amoeba repository somewhere.
>
> It is. It also assumes the old runtime with direct struct access, so not too useful these days. However, it really isn't complicated. It just checks to make sure it's really a subclass, checks that the instance sizes are equal, and then assigns to the isa. Would be trivial to rewrite for the modern runtime API using class_getInstanceSize and object_setClass.

A working example of this trick can be found in xmppframework. It's not my code, but I believe it implements basically the technique you're discussing using the modern framework:

http://code.google.com/p/xmppframework/source/browse/trunk/XMPPMessage.m

--
Jon Olson / @jonolson
Ballistic Pigeon, LLC
ballisticpigeon.com

Reply all
Reply to author
Forward
0 new messages