Paging UIScrollView in increments smaller than frame size

I have a scroll view that is the width of the screen but only about 70 pixels high. It contains many 50 x 50 icons (with space around them) that I want the user to be able to choose from. But I always want the scroll view to behave in a paged manner, always stopping with an icon in the exact center.

If the icons were the width of the screen this wouldn't be a problem because the UIScrollView's paging would take care of it. But because my little icons are much less than the content size, it doesn't work.

I've seen this behavior before in an app call AllRecipes. I just don't know how to do it.

How do I get paging on a per-icon sized basis to work?


Try making your scrollview less than the size of the screen (width-wise), but uncheck the "Clip Subviews" checkbox in IB. Then, overlay a transparent, userInteractionEnabled = NO view on top of it (at full width), which overrides hitTest:withEvent: to return your scroll view. That should give you what you're looking for. See this answer for more details.


There is also another solution wich is probably a little bit better than overlaying scroll view with another view and overriding hitTest.

You can subclass UIScrollView and override its pointInside. Then scroll view can respond for touches outside its frame. Of course the rest is the same.

@interface PagingScrollView : UIScrollView {

    UIEdgeInsets responseInsets;
}

@property (nonatomic, assign) UIEdgeInsets responseInsets;

@end


@implementation PagingScrollView

@synthesize responseInsets;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint parentLocation = [self convertPoint:point toView:[self superview]];
    CGRect responseRect = self.frame;
    responseRect.origin.x -= responseInsets.left;
    responseRect.origin.y -= responseInsets.top;
    responseRect.size.width += (responseInsets.left + responseInsets.right);
    responseRect.size.height += (responseInsets.top + responseInsets.bottom);

    return CGRectContainsPoint(responseRect, parentLocation);
}

@end

I see a lot of solutions, but they are very complex. A much easier way to have small pages but still keep all area scrollable, is to make the scroll smaller and move the scrollView.panGestureRecognizer to your parent view. These are the steps:

  1. Reduce your scrollView sizeScrollView size is smaller than parent

  2. Make sure your scroll view is paginated and does not clip subview enter image description here

  3. In code, move the scrollview pan gesture to the parent container view that is full width:

    override func viewDidLoad() {
        super.viewDidLoad()
        statsView.addGestureRecognizer(statsScrollView.panGestureRecognizer)
    }