Extending UIScrollView

written by   Arvid Janson

UIScrollView is a wonderful class for most of your scrolling purposes, but unfortunately there is no support for snapping to specific points. Unless…

When I set out to build the first version of Viary, we needed a way for the client to indicate their current “mood”. The concept we were creating could be solved with a UISlider, but as I wanted the possible values visible at all times I decided to go another way, and let the user slide the values instead of the indicator.

I initially assumed that the regular UIScrollView would suffice, but as soon as I got to work I realized the that we needed to bring out the old magic hat and pull out a trick or two to get what wanted result: as this was supposed to be a discrete selection, we also needed the content view to snap to specific points.

The problem was that while UIScrollView implement the concept of paging, it only does so with “pages” as wide as the scroll view itself. In other words, the touchable area–with the standard UIScrollView–is only as large as the actual scroll view, which would make interaction awkward to say the least. We needed a way to expand this area, while still keeping the paging around.

Well, as it turns out – while we can’t expand the actual touchable area of the UIScrollView, we can let our application think that we have. Instead of expanding the actual area, we can wrap the UIScrollView in a container, and then relay the touches for the container to the actual scroll view.

To override this behavior we need to subclass UIView, and then add the actual scrollview as a property to this new view. To give the illusion that they are one and the same, we override the hitTest function of the container, to return the child scroll view instead.

#import <UIKit/UIKit.h>

@interface ExtendedScrollView : UIView {
    UIScrollView *scrollView;
}
@property (nonatomic, retain) UIScrollView *scrollView;
@end
#import "ExtendedScrollView.h"

@implementation ExtendedScrollView
@synthesize scrollView;

- (UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if ([self pointInside:point withEvent:event]) {
        return scrollView;
    }
    return nil;
}

- (void)dealloc {
    [scrollView release];
    [super dealloc];
}

@end

When the snapping was in place, finding the selected item was quite trivial: as we never even touched the original UIScrollView in this implementation, non of it regular properties had changed, and contentOffset was calculated based on it’s own coordinate system:

float pageWidth = 50.0f;
int positionOfSlider = (int)(scrollView.contentOffset.x / pageWidth);
NSLog(@"Position of slider: %i", positionOfSlider);