Detect continuous touch position in view with UIScrollView still active - ios

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.

Related

Swift: How to override a method for a scrollView?

I'm trying to override the touchesShouldCancel method for a subclass of a scrollview.
This is my subclass code:
class MyScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
print("Works")
return false
}
}
I have made a scrollview of this class and it scrolls it just doesn't ever print "works". I would like to add some functionality to this but it's never being called. According to the apple documentation:
'The scroll view calls this method just after it starts sending tracking messages to the content view.'
This makes me believe the method should be called whenever I am scrolling and I can't figure out why it's not. Cheers if anyone has any ideas.
From the docs:
Because a scroll view has no scroll bars, it must know whether a touch signals an intent to scroll versus an intent to track a subview in the content. To make this determination, it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement. If the timer fires without a significant change in position, the scroll view sends tracking events to the touched subview of the content view. If the user then drags their finger far enough before the timer elapses, the scroll view cancels any tracking in the subview and performs the scrolling itself. Subclasses can override the touchesShouldBegin(_:with:in:), isPagingEnabled, and touchesShouldCancel(in:) methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures.
Do this experiment.
Add a subview to your scrollview that intercepts touches (think UIButton, not UILabel).
Make sure your content is actually scrollable
Press down on the button and wait for just a moment, then start sliding your finger.
When you tap down on the scroll view and start dragging right away, it assumes you want to scroll the scroll view and not interact with the button.
However when you do that slight pause, it wants to determine if you're trying to interact with the scroll view or with the button.
touchesShouldCancel will then fire.

UIScrollView scrolling using a gesture on the overlaid view

Consider the view hierarchy in the figure below:
OverlayView is simply a control view that has some custom controls on it. It also has multiple tap/swipe gesture recognizers. The ScrollView is the scroll view that needs to be scrolled based on the interactions with OverlayView. The OverlayView has the same frame as that of the ScrollView.
I need a way to add some kind of swipe/pan gesture setup configured for the OverlayView such that I can scroll the underlying ScrollView as if I am interacting with it.
There seems to be two different approaches:
Recognize the gesture on the OverlayView and pass it to ScrollView. But I am unsure what gestures to use and how to make ScrollView interact with them.
Ignore all the touches on the OverlayView unless they are on the controls. Pass these touches to the underlying ScrollView. This seems like the easier approach; but I am not sure how to proceed with this either.
Does anybody have any kind of sample code for some project or a similar exercise they worked on before? If not, any pointers whatsoever?
Option 2 is what I was going to recommend. There is a method you can override called hitTest:withEvent: on UIView.
You return a view from it. So if the touch needs to go through to the scroll view then return the scroll view. Else return self.

make superview respond to subView's UIPanGesture in some cases

I have a scrollView and it contains a subView. SubView has a panGesture added to it.
What I want:
I need pangesture to work on subview only if the panning is done vertically. If the panning happens horizontally, I want the scrollView to respond for the panning instead of subview. How can I acheive this
Note: Iam able to find programatically when the panning happens horizontally.
There are several ways to do this:
Depending on what your subview does, you maybe able to replace it with a scrollview and lock it to scroll only vertically. (while the super should scroll only horizontally).
You can try feeding the touch information from the subview to it's superview if the pan gesture is moving horizontally. You could do this using a delegate pattern or, if you know what you're superview is, just access superview in your subview. However, this creates a strong coupling between the two views and probably shouldn't be done. It would be fragile and many would consider it code smell.
(best option) - Subclass UIGestureRecognizer and create your own gesture recognizer that only recognizes vertical movement. Use that instead of the UIPanGestureRecognizer.
Option 3 is the best, but probably the most work. You'll want to read Apple's docs on subclassing UIGestureRecognizer: Apple Docs - UIGestureRecognizer.
I've also written my own drag gesture recognizer, FlxDragGestureRecognizer.m, FlxDragGestureRecognizer.h (similar to a pan gesture recognizer), which you can use if you'd like, or take a look at to get a good idea of how to subclass UIGestureRecognizer. You can use this class to recognize touches moving only in certain directions, otherwise it will fail (which will allow other gesture recognizer to recognizer, like the scrollview). It also has a lot of other customizations and information it gathers, like velocity.
Since you already know how to detect the panning, then in you will need to implement a delegate protocol in your superview, a delegate property in your subview and assign your superview as a delegate for your subview.
when detecting horizontal panning, call the delegate method, when detecting vertical panning, implement what you want in the subview.

ios move touch event between two uiscrollview

I'm building an iOS layout which consists of a UITableView and a UIScrollView. The UIScrollView is inside a table cell of the UITableView and can be scrolled both horizontally and vertically. The diagram below shows this situation. If the user begins scrolling down/up on the UIScrollView the scrolling event should trigger setContentOffset of the table view, and not setContentOffset for the scroll view while the top of the scroll view will be on the dotted line (it's constant height). Then a scrolling touch event should trigger setContentOffset for the scroll view, not for the table view.
In another case: When the user starts scrolling on the table view, it should trigger setContentOffset for the table view, until the scroll view reaches the dotted line. Then the scroll view should handle setContentOffset.
My problem is how to transfer touch events between the table view and the scroll view during one sliding action.
This sounds like one of those cases where you want something quite specific and custom. So trying to do something clever with the gesture recognizers won't be enough.
The main problem is that the ways you can control gesture recognizers such as with gestureRecognizer:shouldReceiveTouch: and gestureRecognizerShouldBegin: only affect the start of the gesture (or for new touches, not ongoing ones), but you want a single ongoing gesture to transition between controlling each view. So for this reason I think you will need to place a large transparent view over your entire screen with a pan gesture recognizer on it and in your handlePan method decide which view you want to adjust and then call setContentOffset directly on that view. You can use the translation of the pan recognizer and the existing content offset to calculate the new one. I know this isn't very elegant, but I can't think of another way to achieve the effect you want.
I'm not sure if this is going to work, but you could try doing something like this:
Option
self.scrollView.panGestureRecognizer = self.tableView.panGestureRecognizer;
Option
[self.scrollView addGestureRecognizer:self.tableView.panGestureRecognizer];
Option
[self.tableView.panGestureRecognizer requireGestureRecognizerToFail:self.scrollView.panGestureRecognizer];

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