Just a thought... try making the scroll view's bounds smaller than the screen, and fiddle around with getting the views to display properly. (and set scroll view's clipsToBounds to NO)
–
mjhoyAug 3 '09 at 1:28

9 Answers
9

A UIScrollView with paging enabled will stop at multiples of its frame width (or height). So the first step is to figure out how wide you want your pages to be. Make that the width of the UIScrollView. Then, set your subview's sizes however big you need them to be, and set their centers based on multiples of the UIScrollView's width.

Then, since you want to see the other pages, of course, set clipsToBounds to NO as mhjoy stated. The trick part now is getting it to scroll when the user starts the drag outside the range of the UIScrollView's frame. My solution (when I had to do this very recently) was as follows:

Create a UIView subclass (i.e. ClipView) that will contain the UIScrollView and it's subviews. Essentially, it should have the frame of what you would assume the UIScrollView would have under normal circumstances. Place the UIScrollView in the center of the ClipView. Make sure the ClipView's clipsToBounds is set to YES if its width is less than that of its parent view. Also, the ClipView needs a reference to the UIScrollView.

The final step is to override - (UIView *)hitTest:withEvent: inside the ClipView.

This basically expands the touch area of the UIScrollView to the frame of its parent's view, exactly what you need.

Another option would be to subclass UIScrollView and override its - (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event method, however you will still need a container view to do the clipping, and it may be difficult to determine when to return YES based only on the UIScrollView's frame.

Great answer! It worked perfectly. Thank you so much.
–
Jonathan SterlingAug 12 '09 at 18:09

1

Omg I have been trying to do this for days. Thank you so much!
–
respectTheCodeApr 4 '10 at 21:31

1

Great answer, I should have thought of this!
–
DevDevDevApr 22 '10 at 23:25

3

Thanks Ed. I went with a UIScrollView subclass for lazy loading of views (in layoutSubviews), and used a clippingRect to do the pointInside:withEvent: testing. Works really well, and no additional container view required.
–
ohhorobJun 1 '10 at 19:41

5

For those coming across this more recently be aware that - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset added to UIScrollViewDelegate in iOS5 means you don't have to go through this palaver any more.
–
Mike PollardMay 9 '13 at 8:59

The ClipView solution above worked for me, but I had to do a different -[UIView hitTest:withEvent:] implementation. Ed Marty's version didn't get user interaction working with vertical scrollviews I have inside the horizontal one.

This also helps get buttons and other subviews with user interaction working. :) thanks
–
Thomas ClaysonMar 28 '11 at 9:29

3

Thank you for this code, how can I use this modification to get events from subviews (buttons) not contained in the scrollview boundaries? It works if a button for example is within the boundaries of the central scrollview but not outside.
–
DreamOfMirrorsMay 17 '11 at 9:48

3

This really helped. It should be included as a modification of the selected answer.
–
PacuJun 22 '11 at 22:50

1

Yes! This also makes user interaction within the scrollview work again! I did have to set userInteractionEnabled to YES on the ClipView in order for this to work.
–
Tom van ZummerenNov 28 '11 at 13:48

I wound up going with the custom UIScrollView myself as it was the quickest and simpler method it seemed to me. However, I didn't see any exact code so figured I would share. My needs were for a UIScrollView that had small content and therefore the UIScrollView itself was small to achieve the paging affect. As the post states you can't swipe across. But now you can.

Create a class CustomScrollView and subclass UIScrollView. Then all you need to do is add this into the .m file:

I have another potentially useful modification for the ClipView hitTest implementation. I didn't like having to provide a UIScrollView reference to the ClipView. My implementation below allows you to re-use the ClipView class to expand the hit-test area of anything, and not have to supply it with a reference.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (([self pointInside:point withEvent:event]) && (self.subviews.count >= 1))
{
// An extended-hit view should only have one sub-view, or make sure the
// first subview is the one you want to expand the hit test for.
return [self.subviews objectAtIndex:0];
}
return nil;
}

I implemented the upvoted suggestion above, but the UICollectionView I was using considered anything out of the frame to be off the screen. This caused nearby cells to only render out of bounds when the user was scrolling toward them, which wasn't ideal.

What I ended up doing was emulating the behavior of a scrollview by adding the method below to the delegate (or UICollectionViewLayout).

This avoids the delegation of the the swipe action entirely, which was also a bonus. The UIScrollViewDelegate has a similar method called scrollViewWillEndDragging:withVelocity:targetContentOffset: which could be used to page UITableViews and UIScrollViews.

Dave, I was struggling to achieve such a behaviour using UICollectionView as well and I ended up using same approach that you describe here. However, scrolling experience is not as good as it was a scrollview with paging enabled. When I swiped with bigger velocity, several pages were scrolled and it stopped on proposed page. What I want to achieve is save behaviour as normal scroll view with paging enabled - whatever velocity I use, I will get only 1 page more. Do you have any idea how to do it?
–
TankistaJan 25 '13 at 14:30

To get tapping to work on a UIButton embedded in the subview, I replaced the code hitView = [childViews objectAtIndex:i]; with //find the UIButton for (UIView *paneView in [[childViews objectAtIndex:i] subviews]) { if ([paneView isKindOfClass:[UIButton class]]) return paneView; }
–
CharlesAOct 17 '13 at 12:42

This solved my problem :) Just note that you're not adding the scroll offset to point.x when you're checking the bounds, and this makes the code doesn't work well on horizontal scrolls. After adding that, it worked just fine!
–
BartserkJul 10 '13 at 11:08