Can't transition from UIScrollView to UIPanGesture in one movement - ios

What I want to achieve: http://youtu.be/SUWxVtpc0JA
What I can achieve: http://youtu.be/b1xH6Xsvxcg
I’m trying to do the same vertical parallax scrolling effect that the Year Walk Companion app does when you change from text page to menu.
I’ve been able to do this effect on UIScrollViews that only move horizontally, by using a UIPanGestureRecognizer to move up and down, (imagine swiping between nested arrays). However when I have a UIScrollView with the vertical scrolling it breaks.
When I scroll down the UIScrollView it moves as you would expect, same if you move up. However the issues start when I want the UIPanGestureRecognizer to come in and move the UIScrollView itself vertically, not it’s content. To do this so far I have subclassed UIScrollView to make a test on the function gestureRecognizerShouldBegin :
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
UIPanGestureRecognizer* pan = (UIPanGestureRecognizer*)gestureRecognizer;
CGPoint panGestureTranslation = [pan translationInView:self];
if (self.contentOffset.y == 0 && panGestureTranslation.y > 0 ) {
return NO;
}
return YES;
}
It returns NO when my UIScrollView is at the top, and allows my other UIPanGesture for the parallax scrolling between pages to start again.
My problem is, when my UIScrollView is scrolling and reached the top, the function gestureRecognizerShouldBegin will not be called and force me to release the touch before allowing the UIPanGestureRecognizer to parallax scroll.
The question: How can I stop the handle of the touch by the UIScrollView and let the UIPanGestureRecognizer get the touch going up without release my finger from the screen? (like in the Year Walk Companion app).

gestureRecognizerShouldBegin isn't called because a new touch 'session' hasn't started. The existing one is still in progress and the gesture said it didn't want to start.
You could create your own gesture subclass which stays possible and only starts when the content offset < 0. Or you could use the scroll view delegate methods as your input and not use gestures at all.

Related

Detect continuous touch position in view with UIScrollView still active

I am interested in getting the touch location (e.g. something that is, or mimics touchesMoved) in a view controller's view while still keeping a UIScrollView subview enabled. Since all of the touchesDidSomething methods are consumed by the UIScrollView, my hope is that there's a roundabout way of achieving this.
Here are a few things I've tried:
Subclassing a UIScrollView, overriding it's touchesMoved method and passing that touch information to a custom delegate method in my scrollView's view controller. --> This actually works if I deselect "cancellable content touches" and "delays content touches" on my scroll view but it prevents my scroll view from scrolling.
Using the same tactic as above but with a subclassed UIView as a sibling to my UIScrollView. Hence, the hierarchy is as follows:
view
UIScrollView
Subview
Subview
CustomUIView <-- custom UIView that calls delegate
Both of these methods work to the extent that I can grab the data, but at the expense of my scroll view not scrolling anymore. I know I can grab the location of a touch event in a UIScrollView similar to a touchesBegan while the scrollView continues to work but I haven't found a way to get continuous touch events while scrolling. Is this possible?
Here's an illustration of what I'm after:
For some metadata as to why I'm looking for this and why grabbing something like scrollViewDidScroll's contentOffset.x won't work, I'm specifically interested in when the scrollView has scrolled to the end (or beginning) and a user is attempting to keep swiping forward (or backward) even though the scrollView can't scroll in the swiped direction anymore. When this happens I want to detect the forward (or backward) swipe motion to initiate a slick transition to another view.
I solved this by adding a panning gesture recognizer to my view and using it's translationInView method (UISwipeGestureRecognizer doesn't have this method). Example below
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(myMethod(_:)))
panGesture.delegate = self
self.addGestureRecognizer(panGesture)
func myMethod(sender: UIPanGestureRecognizer) {
// sender.translationInView(self).x
}
Also making sure to return true for shouldRecognizeSimultaneouslyWithGestureRecognizer in the UIGestureRegognizerDelegate to account for the scrollView.

Restrict angle of swipe to scroll UIScrollView

I have a vertical scroll view that needs to be scrollable but I want to use the left and right swipe gestures for something else. I have the behavior for each swipe direction working, but I want to restrict the angle of a swipe that scrolls the UIScrollView, so that it has to be really, really vertical, and most left or right leaning swipes activate the other behavior and do not change the scroll position.
I know that I can get the swipe angle by overriding scrollViewDidScroll and comparing the previous and current contentOffset, but I cannot stop the scrollview from scrolling there.
How can I limit the angle of a swipe that scrolls a UIScrollView ?
I think scrollViewDidScroll is too late for what you want to achieve.
Maybe try setting the directionalLockEnabled property of the UIScrollView to YES.(not sure if this helps... but play with it)
And/Or implement the UIScrollViewDelegate method - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView and within this methods implementation grab a pointer to the panGestureRecognizer (readonly) property of the UIScrollView passed in.
From there call translationInView: on the UIPanGestureRecognizer, passing in the view in whose coordinate system the translation of the pan gesture should be computed, and with the CGPoint returned use trigonometry to calculate the angle and if it's not within predetermined limits call setTranslationInView: on the UIPanGestureRecognizer to restore.

Limit UITableView panning to 1 finger

I have a scenario where I have two UITableViews as a subview in a main UIView:
UIView (frame = full screen)
+--- UITableView (frame = ~1/3 of the screen)
+--- UITableView (frame = second ~1/3 of the screen)
I want to detect a three finger swipe up on the whole screen area (and also allow the user to pan UITableViews up and down with at least one finger).
I have a UISwipeGestureRecognizer attached to UIView with numberOfTouchesRequired = 3.
I've tried these:
Setting both internal UIPanGestureRecognizers maximumNumberOfTouches to 1 on both UITableViews. To my understanding this should prevent two and three finger pans on the UITableViews but it doesn't. (If I set enabled to NO on these UIPanGestureRecognizers, the touches are correctly passed to the superview. But then the panning/scrolling doesn't work.)
Calling panGestureRecognizer requireGestureRecognizerToFail: with my UISwipeGestureRecognizer on both UITableViews. This works partly, but the panning waits until the swipe hasn't been completed and it feels very clumsy.
Overriding UITableView with setting shouldRecognizeSimultaneouslyWithGestureRecognizer: to return YES, which allows me to detect the three finger swipe. However, the UITableViews beneath pan/scroll up unintentionally.
So how do I limit the number of panning touches to 1 (or 2) and let the three-finger UISwipeGestureRecognizer recognize three finger swipes?
Try overriding canPreventGestureRecognizer: on the top (whole screen) UIPanGestureRecognizer, returning NO for each of the two table view gesture recognizers.
I'd also try overriding canBePreventedByGestureRecognizer: on each of the two table view gesture recognizers to return NO in the case of the topmost UIPanGestureRecognizer.
I've run into a similar situation as yourself quite a while ago and can't remember how I fixed it (the project is long gone), but I seem to remember playing around with the aforementioned methods and eventually getting it to work.

UIPanGestureRecognizer with UIScrollView

I've been fighting with UIScrollView for a while now.
I have a scroll view which has multiple 'card' style views in it. I want the cards to move vertically, as in swiping up and down.
Imagine MobileSafari's tab view, but with swipe down to close tabs.
I can't figure out how to do this without it conflicting with the scroll view horizontal panning. I either get them both panning, or only one panning (vertical / horizontal).
What's the best practice to make this work perfectly, as in "if you're swiping vertically, stop horizontal swiping, and if you're swiping horizontally, stop vertical swiping".
Thank you!
Here's an illustration of what I want :
The scroll view has its own gesture recognizer: see its panGestureRecognizer property. If you add your own recognizer for detecting the vertical swipe, you can use requireGestureRecognizerToFail: or delegate methods to manage dependencies between the two recognizers.
I'm really confused by the intended behaviour of your app here. You are using swipe and panning interchangeably but they are different gesture recognizers.
Well one way to differentiate is to compare the x and y values of the translationInView: method of the gesture. If y > x, you have a vertical swipe/pan; x > y and you have a horizontal swipe/pan.
Make that gesture recognizer fail if the swipe/pan detected is not the type you are looking out for.

How do I pass delayed scroll gestures from a UIButton inside of a UIScrollView?

I have a UIScrollView that contains several UIButtons. Each button is wired up to take an action when the user inputs a touch up event, so they are able to place their finger on the button and it will not be selected until it is raised. Currently, if I made a swipe gesture to scroll the UIScrollView quickly, then the scroll view moves as expected even if the gesture happens directly over a UIButton. HOWEVER, if I hold my finger down too long on a UIButton (about 1 second), the UIScrollView will no longer recognize the gesture and will not be able to scroll until the finger is lifted up.
I am wondering if their is a way to always have the UIScrollView recognize the scroll gesture? Note that this is not an issue if I touch the UiScrollView in a location without a UIButton - it then scrolls as expected.
It may worth a try to let your UIButton respond to UIControlEventTouchDown (maybe with an empty action). I'm not sure if this will work, but conceptually I think it should let the UIButton capture the touch immediately.
(Also make sure you don't enable delaysContentTouches on your scrollview.)
I found the answer to this here: UIScrollview with UIButtons - how to recreate springboard?
Essentially, I had to extend UIScrollView and override touchesShouldCancelInContentView, having it always return YES.

Resources