Ad

Our DNA is written in Swift
Jump

NSScrollView contained in NSScrollView

For an inspector panel I wanted to have a horizontal collection view contained inside a vertical inspector scroll view. The vertical scroll view would only scroll if the window was too small to show all sections in the inspector.

The problem there is a NSScrollView gobbles up all scroll wheel events if the mouse pointer is on top of it. Here’s a solution how to have it selectively forward the scroll events up the responder chain.

Scroll events are sent to NSScrollViews by means of the scrollWheel selector. Those events could potentially have a delta for X, Y and Z, though I have no idea where a Z axis would be on my Magic Mouse.

The solution is to have a subclass of NSScrollView selectively forward the kinds of scroll events that don’t concern it. I chose the usesPredominantAxisScrolling property in combination with the hasHorizontalScroller/hasVerticalScroller to make this decision so that I don’t need to introduce another property. This way if you disable a vertical scroll bar and activate the predominant axis scrolling in Interface Builder you made your intent clear of not having this scroll view scroll vertically.

@implementation DTScrollView
 
- (void)scrollWheel:(NSEvent *)theEvent
{
	BOOL shouldForwardScroll = NO;
 
	if (self.usesPredominantAxisScrolling)
	{
		if (fabs(theEvent.deltaX)>fabs(theEvent.deltaY))
		{
			// horizontal scroll
			if (!self.hasHorizontalScroller)
			{
				shouldForwardScroll = YES;
			}
		}
		else
		{
			// vertical scroll
			if (!self.hasVerticalScroller)
			{
				shouldForwardScroll = YES;
			}
		}
	}
 
	if (shouldForwardScroll)
	{
 
		[[self nextResponder] scrollWheel:theEvent];
	}
	else
	{
		[super scrollWheel:theEvent];
	}
}
 
@end

Initially I had an outlet for a view to forward the events to, but it turns out that this is not necessary because the responder chain will forward the event just the same. Searching in the headers you will find that scrollWheel: is defined in NSResponder.h and the default implementation for views is to forward it to their own next responder.

Without the above code I found that even though my inner scroll view does not scroll vertically, the outer scroll view would never scroll while my mouse pointer was hovering over the inner one.

On iOS Apple has already successfully addressed this problem and you can have this combination of vertical and horizontal scroll views without any tricks. On OS X Apple still has some work to do.


Categories: Recipes

1 Comment »

  1. Thanks! It’s been really helpful.