UIScrollView: Only scroll when allowed - ios

I've been trying to only have my UIScrollView scroll in one direction and still get all UITouch events. I'm not fully sure this is possible, at all.
I've tried to override the UIResponder's "touches" events, but as soon as the UIScrollView starts scrolling, the touches methods stop getting called.
I then tried setting the UIScrollView's scrollEnabled property to false until I want the UIScrollView to begin receiving drag events, but I can't seem to find a way to have the UIScrollView to begin receiving touch events mid-drag. Is this possible?
My next attempt was adding "padding" to the scroll view and creating my own "touch events" by playing around with the contentOffset property, but that started calling scrollViewDidScroll: an excessive amount of times and processing like crazy - also some issues with the validity of the responses.
I've played around with hitTest:withEvent:, pointInside:withEvent:, touchesShouldBegin:withEvent:inContentView: and touchesShouldCancelInContentView: without any success.
I've tried a few other things, since, but none have worked. Also, from what I've found, setting delaysContentTouches to false destroys the scroll response.
I would love some real overridden touches methods. Methods that if I don't call super, the UIScrollView doesn't receive the touches.
I need a way to get UITouches (Drags, begins, releases and cancels, not just taps), without affecting the scroll of the UIScrollView, since the UIResponder "touches" events drop off as soon as scrolling starts. Is this possible?

If you just want to detect common touches, try attaching gesture recognizers like UITapGestureRecognizer to your view, they should work even with a UIScrollView.
You can also try placing a transparent UIView on top of the scrollview that detects some touches and passes the rest to the scrollview.

When you start to scroll save off a copy of the offset. Then check against it when you finish scrolling. This code allows scrolling to the left but prevents it from scrolling right
CGPoint oldOffset;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
oldOffset = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x < oldOffset.x) {
[scrollView setContentOffset:oldOffset];
}
}

Related

Detect when a user interrupted UIScrollView animation

I have an app with a horizontal scrolling uiscrollview, it shows 3 subviews side by side. I am using Objective C.
When the user drags and releases, the code detects which is the closest subview and automatically scrolls to the nearest of the 3 subviews, making it fully centred.
This all works fine, except;
When the scrollview is animating an auto-centering move, the user can interrupt by just tapping it (not dragging).
This single tap halts the animation and scrollview, leaving it not centred on any of the 3 subviews.
No delegate method I have found can detect when the animation is interrupted and then continue with centering.
How can I do this?
When you start the scroll animation with setContentOffset:animated: or scrollToRectVisible:animated:, its end will be marked by UIScrollViewDelegate method scrollViewDidEndScrollingAnimation:. If before that other delegate method is called, like scrollViewWillBeginDragging: or scrollViewDidEndDecelerating: - that means that user interrupted your animatin with drag (or a tap).
What you want to do is restart your animation when user stops dragging - in scrollViewDidEndDragging:willDecelerate: or scrollViewDidEndDecelerating:
Thanks again mag_zbc!
This really helped me find the solution.
I was overcomplicating the animation, checking for the current position both after the user finished dragging as well as when the animation finished.
To solve it, I just checked the current offset in scrollViewDidEndDecelerating::
-If the final offset was one of the three correct locations (auto-offset finished uninterrupted) then do nothing.
-If the offset was not one of the correct locations (animation was interrupted), then move again.

Often Inadvertently enter one of ScrollView's sub-items when scrolling the ScrollView

When I am scrolling a ScrollView, it's very often that it enters the item of the ScrollView by accident.
How can I solve this problem?
Is it because of the scroll gesture duration?
How can I more clearly identify between the tap-gesture and scroll-gesture?
Update: I finally found it is because I set the sub-item's gesture as "TouchDown" but not "TouchUpInside". So that's my reason.
You should use a UIScrollView property.....which will delay to respond its subitems....
scrollViewObj.delaysContentTouches = YES;
This will respond its sub-items after the scroll of your scrollview finishes.

UIPicker within UIScrollView - Ignore drags/moves but capture touches

I have a rather large (in screen-estate) UIControl that I'm putting into a UIScrollView. Being a UIControl, it hijacks all touches and any drag that starts from within the control will not be reflected by the UIScrollView.
This control only needs UIControlEventTouchUpInside, so is there any way to "only" listen to that event?
My first instinct was to override hitTest:withEvent: and return the superview if it detected a drag, but that doesn't provide any information on the type of control event.
My last resort is to make it a generic UIView, but I'd rather not go down that road if possible.
EDIT:
Sorry, for clarity, I purposefully set delaysContentTouches to NO to support other features on the screen I am working on. The unintended side effects result in Joel H's second point.
Try setting both the canCancelContentTouches and delaysContentTouches properties of your UIScrollView to YES.
When the UIScrollView property canCancelContentTouches is set to YES, it should transfer any scrolling/drag touches to the scroll view and cancel whatever that touch would have been registered in the subview.
The scroll view's delaysContentTouches should be set to YES as well. This will prevent drags from being triggered as taps (because the handler fires off too soon).
UIScrollView class reference

UIScrollView gets disabled in one direction - iOS

I have a UIScrollView with no vertical scrolling, and on which I force a particular offset (only x) in case the offset tries to become less that that particular offset.
I use setContentOffset:animated: function, with animated argument as YES. The offset is forced correctly.
After I force the offset, 'sometimes' scrolling in the direction opposite to the initial scroll direction gets blocked. Say, I was scrolling with finger pan from left to right, and forced it to some offset, then I can't scroll from right to left anymore.
But the catch is, if I make any tap on the screen, the scrolling starts happening. I am unable to pan, but if I tap the screen, or tap any button, scrolling starts working. If I try to call the button press method programatically after, say 5 seconds of forcing the offset, then it doesn't work. It seems that I need to touch the screen somehow..
I checked the values of contentOffset, contentSize, they seem fine.
PS: there are times when scrollViewDidEndScrollingAnimation: method is not called after forcing the offset, but that is not necessarily the issue.
Edit: Actually, this thing happens when I take the scroll view beyond the threshold offset using my fingers, and keep panning left. At the threshold point, panning stops, but the next time I try to scroll, it doesn't pan. (i.e., I don't give a jerk to go beyond the threshold).
Edit: One more possible loophole: I make scrollEnabled equal to NO just before forcing the new offfset, and just after giving the command to set the new offset, I set it back to YES. I needs to be done so that if I try to scroll the scrollview with a jerk, it doesn't scroll away to the left while trying to set the new offset, since paging is enabled.
Edit: Could it be because I set scrollEnabled to NO while actually scrolling using touch? I do enable it later, but maybe that is some issue...
Important Edit: If I long press on the scrollView, and then try to move, scroll view starts scrolling!
Edit: This code is in scrollViewDidScroll:
if ((theScrollView.contentOffset.x < theScrollView.frame.size.width)
&& [currentlyDisplayedVC isEqual:VC1])
{
//if this is not done, and this call happens when VC3
//is visible a bit too much, scrollView scrolls till VC3.
scrollView.scrollEnabled = NO;
[scrollView setContentOffset:CGPointMake(scrollView.frame.size.width, 0) animated:YES];
scrollView.scrollEnabled = YES;
//this is done so that this block is not reached everytime during scroll animation.
currentlyDisplayedVC = VC2;
}
Then in scrollViewDidEndWithANimation: I add [self VC2reached]
Have you tried using setUserInteractionEnabled:NO and YES to stop/allowing user to interact with the UIScrollView instead of using the scrollEnabled property?
I subclassed UIScrollView and overrode the method touchesShouldBegin:withEvent:inContentView:. In the method I return NO for that particular moment when I see the view stuck, so that all touches are taken in by the scroll view instead of passing them to any other contentView. That solved the problem.
Edit: That doesn't solve the problem actually. I want to disallow touches on contentView ONLY when an attempt to scroll the scrollview is being made, not when some other event like tap on the contentView is attempted. But in ths olution that I posted, all touches will be kept with the scroll view. Do we have a similar touchMoved: method?
Instead of using setContentOffset:animated:
you should try using scrollRectToVisible:animated:

UIScrollView inside of UIScrollView?

Similar questions have been asked to this, but my predicament is actually the opposite of theirs. I've got a fullscreen, paging UIScrollView, with each page being the size of the screen. Inside of that, I've got some pages that are themselves UIScrollViews, with their width greater than the screen width. The inner scrollviews are within a few layers of other UIViews, they are not direct subviews of the outer scrollview.
What I'd like is that, when I get to the end of one of the inner scrollviews, it starts scrolling the outer scrollview. From other questions I see on here, it looks like that should happen, but for some reason it doesn't. What in my setup could be causing this to stop happening? Where in the touch stack is the hand-off supposed to happen between inner and outer scrolling?
edit: Is there any way to pass touches or pan gesture commands out to the outer scroll view using I sense the inner view is past its bounds via scrollViewDidScroll delegate method?
You can call the super class touchesBegan delegate as follows
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
}
Here is the reference link: How to pass the touch event to superview when userInteractionEnabled = YES?
WWDC 2013 Session 217 Exploring Scroll Views on iOS 7 explains in detail how to nest scroll views.

Resources