Is there a proper way to handle overlapping NSView siblings?

Chris, the only solution is to use CALayers. That is definitely the one and only solution.

NSViews are plain broken in OSX (Sept 2010): siblings don’t work properly. One or the other will randomly appear on top.

Just to repeat, the problem is with siblings.

To test this: using NSViews and/or nsimageviews. Make an app with a view that is one large image (1000×1000 say). In the view, put three or four small images/NSViews here and there. Now put another large 1000×1000 image on top. Build and launch the app repeatedly – you’ll see it is plain broken. Often the underneath (small) layers will appear on top of the large covering layer. if you turn on layer-backing on the NSViews, it does not help, no matter what combo you try. So that’s the definitive test.

You have to abandon NSViews and use CALayers and that’s that.

The only annoyance with CALayers is that you can’t use the IB to set up your stuff. You have to set all the layer positions in code,

yy = [CALayer layer];
yy.frame = CGRectMake(300,300, 300,300);

Make one NSView only, who’s only purpose is to hold your first CALayer (perhaps called ‘rear’), and then just put all your CALayers inside rear.

rear = [CALayer layer];
rear.backgroundColor = CGColorCreateGenericRGB( 0.75, 0.75, 0.75, 1.0 );

[yourOnlyNsView setLayer:rear]; // these two lines must be in this order
[yourOnlyNsView setWantsLayer:YES]; // these two lines must be in this order

[rear addSublayer:rr];
[rear addSublayer:yy];
     [yy addSublayer:s1];
     [yy addSublayer:s2];
     [yy addSublayer:s3];
     [yy addSublayer:s4];
[rear addSublayer:tt];
[rear addSublayer:ff];

everything then works utterly perfectly, you can nest and group anything you want and it all works flawlessly with everything properly appearing above/below everything it should appear above/below, no matter how complex your structure. Later you can do anything to the layers, or shuffle things around in the typcial manner,

-(void) shuff
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:0.0f]
              forKey:kCATransactionAnimationDuration];
if ..
    [rear insertSublayer:ff below:yy];
else
    [rear insertSublayer:ff above:yy];
[CATransaction commit];
}

(The only reason for the annoying ‘in zero seconds’ wrapper for everything you do, is to prevent the animation which is given to you for free – unless you want the animation!)

By the way in this quote from Apple,

For performance reasons, Cocoa does
not enforce clipping among sibling
views or guarantee correct
invalidation and drawing behavior when
sibling views overlap.

Their following sentence …

If you want a view to be drawn in
front of another view, you should make
the front view a subview (or
descendant) of the rear view.

Is largely nonsensical (you can’t necessarily replace siblings with subs; and the obvious bug described in the test above still exists).

So it’s CALayers! Enjoy!

Leave a Comment