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.
Related
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.
I'm not sure if this is possible, but I have a view that is able to be dragged around the screen via pan gestures. Once the view is selected, little grippers appear on the corners of the view that allow the user to resize the view. The problem is, those grippers go outside the bounds of the view (they still show up, because clipSubviews is off), but gesture recognizers on those grippers are not firing when selecting the part of them that is drawn outside of the view. Making the view bigger to actually hold the grippers would break a lot of already created logic that is based on the size of the view, so that is a last resort for me.
Is there any other way to get gesture recognizers to work on views that are drawn outside of their parent view?
You could try overriding hitTest:withEvent: in a UIView subclass, and return the gripper view.
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.
I have a scroll view that has a variable number of UIImageView subviews. I subclassed UIScrollView because I want to be able to move and resize the images with gestures, and do some other custom behavior. As soon as I switch the scroll view to my subclass in the nib file, some strange things happen: the scroll view will expand vertically and cover other views when scrolled upward, and the bottom edge stops short of the full screen and leaves a big gap...but when I change it back to a regular UIScrollView, it's fine. I don't override anything in my subclass or set anything in the nib that I believe could cause this...all I do is override the addSubview method, and add gesture recognizers to subviews as they are added to my scroll view, and of course have methods to handle those gesture recognizers. Any ideas as to what I'm doing wrong?
Thanks in advance!
Got it! The problem, it seems, was that I had a method called handlePan...by doing this I had inadvertently overridden an identically-named method of UIScrollView. So, my handlePan (which I had intended only to handle the pans for my subviews) was instead handling all pans, including the scrollview's built-in one, and causing the weird scrolling. Whoops! Problem solved.
I have a view with three image views hangin' around. These image views respond to certain gestures. I want to allow a certain gesture (such as a swipe across the screen) to do an action to all of the imageviews (such as, say, delete them all).
The only way I can think of having the swipe gesture be recognized everywhere on the screen is by overlaying a clear superview that looks for swipe gestures. My problem; however, is that I don't know how to let the superview ignore all other gestures so I can still interact with the imageviews below. Is there an easier way to handle this problem?
Try adding the gesture to your view's window rather than the view:
[self.view.window addGestureRecognizer:gesture];