How can I force a UIScrollView
in which paging and scrolling are on to only move vertically or horizontally at a given moment?
My understanding is that
What I did was create a paging UIScrollView with a frame the size of the view screen and set the content size to the width needed for all of my content and a height of 1 (thanks Tonetel). Then I created menu UIScrollView pages with frames set to each page, the size of the view screen, and set the content size of each to the width of the screen and the necessary height for each one. Then I simply added each of the menu pages to the paging view. Make sure paging is enabled on the paging scroll view but disabled on the menu views (should be disabled by default) and you should be good to go.
The paging scroll view now scrolls horizontally, but not vertically because of the content height. Each page scrolls vertically but not horizontally because of its content size constraints as well.
Here's the code if that explanation left something to be desired:
UIScrollView *newPagingScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
[newPagingScrollView setPagingEnabled:YES];
[newPagingScrollView setShowsVerticalScrollIndicator:NO];
[newPagingScrollView setShowsHorizontalScrollIndicator:NO];
[newPagingScrollView setDelegate:self];
[newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)];
[self.view addSubview:newPagingScrollView];
float pageX = 0;
for (int i = 0; i < NumberOfDetailPages; i++)
{
CGRect pageFrame = (CGRect)
{
.origin = CGPointMake(pageX, pagingScrollView.bounds.origin.y),
.size = pagingScrollView.bounds.size
};
UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here
[pagingScrollView addSubview:newPage];
pageX += pageFrame.size.width;
}
I've also had to solve this problem, and while Andrey Tarantsov's answer definitely has a lot of useful information for understanding how UIScrollViews
work, I feel that the solution is a bit over-complicated. My solution, which doesn't involve any subclassing or touch forwarding, is as follows:
UIPanGestureRecognizers
, again one for each direction.UIScrollViews
' panGestureRecognizer properties and the dummy UIPanGestureRecognizer
corresponding to the direction perpendicular to your UIScrollView's desired scrolling direction by using UIGestureRecognizer's
-requireGestureRecognizerToFail:
selector.-gestureRecognizerShouldBegin:
callbacks for your dummy UIPanGestureRecognizers, calculate the initial direction of the pan using the gesture's -translationInView: selector (your UIPanGestureRecognizers
won't call this delegate callback until your touch has translated enough to register as a pan). Allow or disallow your gesture recognizer to begin depending on the calculated direction, which in turn should control whether or not the perpendicular UIScrollView pan gesture recognizer will be allowed to begin.-gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
, don't allow your dummy UIPanGestureRecognizers
to run alongside scrolling (unless you have some extra behavior you wanted to add).This is improved solution of icompot, which was quite unstable for me. This one works fine and it's easy to implement:
- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
self.oldContentOffset = scrollView.contentOffset;
}
- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{
float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x );
float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y );
if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) )
{
scrollView.pagingEnabled = YES;
scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
self.oldContentOffset.y);
}
else
{
scrollView.pagingEnabled = NO;
scrollView.contentOffset = CGPointMake( self.oldContentOffset.x,
scrollView.contentOffset.y);
}
}
- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
self.oldContentOffset = scrollView.contentOffset;
}
For future reference, I built upon Andrey Tanasov's answer and came up with the following solution that works fine.
First define a variable to store the contentOffset and set it to 0,0 coordinates
var positionScroll = CGPointMake(0, 0)
Now implement the following in the scrollView delegate:
override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
// Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method
// For some reason, not doing this fails to get the logic to work
self.positionScroll = (self.scrollView?.contentOffset)!
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
// Check that contentOffset is not nil
// We do this because the scrollViewDidScroll method is called on viewDidLoad
if self.scrollView?.contentOffset != nil {
// The user wants to scroll on the X axis
if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x {
// Reset the Y position of the scrollView to what it was BEFORE scrolling started
self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y);
self.scrollView?.pagingEnabled = true
}
// The user wants to scroll on the Y axis
else {
// Reset the X position of the scrollView to what it was BEFORE scrolling started
self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!);
self.collectionView?.pagingEnabled = false
}
}
}
Hope this helps.
My view consists of 10 horizontal views, and some of those views consist of multiple vertical views, so you'll get something like this:
1.0 2.0 3.1 4.1
2.1 3.1
2.2
2.3
With paging enabled on both horizontal and vertical axis
The view also has the following attributes:
[self setDelaysContentTouches:NO];
[self setMultipleTouchEnabled:NO];
[self setDirectionalLockEnabled:YES];
Now to prevent diagonal scrolling do this:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
int pageWidth = 768;
int pageHeight = 1024;
int subPage =round(self.contentOffset.y / pageHeight);
if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) {
[self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight];
}
}
Works like a charme for me!
If you haven't tried doing this in the 3.0 beta, I recommend you give it a shot. I am currently using a series of table views inside a scroll view, and it works just as expected. (Well, the scrolling does, anyway. Found this question when looking for a fix to a totally different issue...)