Shadows Around Document Views Under Elastic Scrolling On Lion

To break a trend, here’s something technical.

If you run Safari on Lion and pull down past the top of the page you’ll see a blue linen background and a shadow atop the content view. This is because of the new elastic scrolling that Lion has inherited from iOS. By default NSScrollView does not provide this behaviour for you. Moreover it’s not obvious how to implement it.

Creating a custom NSScrollView will fail — NSScrollView doesn’t report a different frame when a document view is offset by an elastic scroll. Neither does it report a different frame for it’s scroll view under those circumstances. This means that we can’t subclass NSScrollView or NSClipView and implement a -drawRect method that will achieve the shadowed content view we’re after.

Here’s how you do it.

Override the -drawRect method of your ScrollViews’ DocumentView. Save the graphics state. Create a new clip path based on outsetting the view’s bounds intersected by the bounds of the superview. Set the new clip path then draw the shadow in the affected regions. Finally, restore the graphics state.

Here’s a simple example:

-(void) drawRect:(NSRect)dirtyRect; { [NSGraphicsContext saveGraphicsState];

NSBezierPath *newClipPath = [NSBezierPath bezierPathWithRect: NSIntersectionRect( NSInsetRect( [self bounds], -5, -5 ), [[self superview] bounds] )];
[newClipPath setClip];

NSBezierPath *outerPath = [NSBezierPath bezierPathWithRect: [self bounds]];

NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowColor: [NSColor blackColor]];
[shadow setShadowBlurRadius: 5];
[shadow set];

[outerPath stroke];
[shadow release];

[NSGraphicsContext restoreGraphicsState];

}

The trick is knowing how things work — the NSClipView that is the super view of your DocumentView has set up a clipping region inside the renderer. It has assumed you’ll not draw outside your bounds. If you want to draw shadows around your DocumentView to look nice during elastic scrolling you’ll need to draw outside your bounds. That’s how you do it.

As a caveat — do not use -setClip unless you know what you’re doing. Use -addClip to decrease the rendering space which is almost always what you want to do. If you find yourself calling -setClip and you’re not 100% sure you know what you’re doing you’ll find that you’ve ended up drawing all over the toolbar and any other UI you’ve got surrounding the view you’ve decided to get cute with.