Order of UIGestureRecognizer touchesBegan(_:with:) and point(inside:with:) - ios

The Question
When the user taps on a view which of these two functions is called first? touchesBegan(_:with:) or point(inside:with:).
The Context
I want to subclass a PKCanvasView (which inherits from UIScrollView) to allow interaction through the view (i.e. allow interaction to the view below and disable interaction to the PKCanvasView) when the point of the touch is outside of the UIBezierPath of a stroke on the canvas.
This is easy enough by overriding point(inside:with:). My issue lies in the fact that I only want to allow interaction to the view below if the touch event UITouch.TouchType is not an apple pencil : .pencil. (so that the user can draw with the apple pencil and interact with the view below using their finger)
The only way I think can get this information is by also overriding touchesBegan(_:with:). Here I can access the event and its touch type. I would then somehow pass this to be read inside of point(inside:with:).
However, that all relies on extracting the UITouch.TouchType information before I check if the touch point is overlapping with any PKStroke paths.
So: Is touchesBegan(_:with:) called before point(inside:with:)?

Related

iOS - forward touches to another view

There is a similar SO Question that exists for this problem but unfortunately there were no suitable answers provided.
I have a google maps view (GMSMapView) that is entirely covered by a transparent sibling view that acts as a container for thumbnail images. The thumbnails are child views of the container view, not the map view. These child views are randomly scattered about the map and therefore partially hide portions of the map's surface.
Tapping on one of these thumbnails triggers a segue to a different VC that shows a zoomed view of the image.
The problem:
Given that these thumbnails lie on top of the map, they prevent the normal map gestures from occurring if the gesture intersects one of the thumbnails. For example, if a user wishes to pinch-zoom, rotate or pan the map and one of his/her fingers begins overtop of a thumbnail, the touches are intercepted by the thumbnail.
Non-starters:
Obviously, I can't set userInteractionEnabled to false on a thumbnail because I need to detect tap gestures to trigger the segue.
I don't think that I can customize the responder chain using UIView's hitTest:withEvent and pointInside:withEvent methods on the thumbnail view because they are not in the same branch in the view hierarchy as the map view AND the dispatching logic is dependent on the type of gesture (which I don't think is available at this point - touchesBegan, etc. are called once the appropriate view has been chosen to receive the event). Please correct me if I'm wrong...
Attempted Solution:
Given the above, the strategy I'm attempting is to overlay all other views in the view controller with a transparent "touch interceptor view". This view's only purposes is to receive all touch messages -- by overriding touchesBegan(), touchesMoved(), touchesEnded() -- and dispatch them to other views as appropriate.
In other words, depending on the type of gesture recognized (tap vs. other), I could call the appropriate target view's (either one of the thumbnails or the map) touchesBegan(), touchesMoved(), touchesEnded() methods directly by forwarding the touches and event parameter.
Unfortunately, while this works when the target view is a simple UIView, it seems most UIView subclasses (including GMSMapView) don't allow forwarding of touch events in this manner; as described in the following article (see section: A Tempting Non-Solution).
Any ideas would be greatly appreciated.

Begin UIPanGesture Event From A Pressed State At Time Of Instantiation

Is there a way to begin a UIPanGestureEvent if the finger is already pressed at the time the object is instantiated?
I have a situation where when a user holds their find on a screen I create a UIView under their finger.
I want them to be able to drag that around and as such I have put a UIPanGestureRecognizer inside the UIView.
Problem is I need to take my finger off and put it back to trigger the UIPanGestureRecognizer to start up. I need it to start from an already pressed state.
Do you know how I can activate a UIPanGesture from an already pressed state i.e. can I get the touch event thats already active at the time of instantiation and pass it along?
You can do it, but the UIPanGestureRecognizer will need to exist already on the view behind the view you create (and you will then have to adjust your calculations based on this; not difficult).
The reason is that, under the circumstances you describe, the touch does not belong to the UIView you create - it belongs to the UIView behind it, the one that the user was originally touching. And given the nature of iOS touch delivery, you can't readily change that. So it will be simpler to let that view, the actual original touch view, do the processing of this touch.
I think Matt's solution is best so I am going to mark it as correct.
However my code structure wasn't going to allow me to cleanly implement it. Compounding the issue was the object listening was listening for a UILongGestureRecognizer.
So my solution was as follows:
Create a callback in my ViewController that would handle the longGestureOverride call
Add a callback to the object listening for the longGesture that would call the longGestureOverride callback and pass along the point
Manually move the object based on the point passed back
If the user lifts their finger, I disable the longGestureOverride callback, and begin using the UIPanGesture inside the new object

How to react to a "touch up inside" event in UIAccessibilityElement subclass?

I have a map with drawn items. The map handles touch events and determines the touched items as if they were buttons.
I made the map a container and implemented the methods to return accessibleElements. For each item I create one instance of a UIAccessibilityElement subclass.
It seems UIAccessibilityAction protocol has no callback for a "tap" or "button pressed" event.
How would I mimic the effect of a UIButton with UIAccessibilityElement then?
Assuming you are running under iOS 5 or iOS 6, consider the following workaround. It is not optimal, but will work until there is a better way:
Create a dummy view that is not, itself, an accessibility element.
On this view, implement your "tap" handling in -touchesEnded:withEvent:.
Set your accessibility element's accessibilityActivationPoint to a value that falls within this dummy view.
Your dummy view will receive touch events when the corresponding accessibility element is activated. Make sure to ignore touch handling in your dummy view if VoiceOver and other assistive technologies are not running.
EDIT: Another less hacky approach is to implement a tap gesture recognizer on the view you're concerned with, convert the coordinate from -touchesEnded:withEvent: to screen coordinates, and manually hit test the point against the frames of your accessibility elements.

Send UIEvent from iOS device UIView to external screen UIView

I'm working on an app using AirPlay and I need a way to have touches on the main screen act as touches on the UI on my external screen in order to be compatible with a large number of previously existing custom UI elements. Rebuilding the UI elements would be orders of magnitude more difficult than finding a way of translating the touches from one view to another.
The external screen will feature a sort of mouse pointer to represent the interaction, as the user will need a point of reference on the screen for their actions. This may create User Interface guidelines hurdles, but I'll cross that mountain when I get to it. Hopefully I can find a way to make this item sufficiently non-mouse like. The user will interact with the device as a sort of track-pad.
I'm using a window on the device screen with "sendEvent" subclassed to catch the touch events. I'm attempting to manually walk the view hierarchy for the views that need to receive input. Finding the view I want to talk to is not difficult. For UIControl based classes I can call "sendActionsForControlEvents" to send the appropriate messages for the control. This may need some caressing, but for now that's not the main issue.
For the UI events for touchesBegan, touchesMoved, etc. I don't have a decent way of faking the UIEvent information. I can't call these functions unless I have a UIEvent, and I don't seem to have any way of create a UIEvent object. The UIEvent from sendEvent does not have a position that matches the pointer position on the secondary screen (at the least), so simply passing it on will not give me what I want.
Is there any legitimate way of synthesizing this information?

Passing touch events to appropriate sibling UIViews

I'm trying to handle touch events with touchesBegan in an overlay to a parent UIView but also allow the touch input to pass through to sibling UIViews underneath. I expected there would be some straight-forward way to process a touch event and then say "now send it to the next responder as if this one didn't exist", but all I can find is the nextResponder method which appears to be giving back the parent to my overlay view. That parent is then not really passing it onto the next sibling of that overlay view so I'm stuck uncertain how to do what seems like a simple task that is usually accomplished with a touch callback that gets a True or False return value to tell it whether to keep processing down the widget hierarchy.
Am I missing something obvious?
Late answer, but I think you would be better off overriding hitTest:withEvent: instead of touchesBegan. It seems to me that touchesBegan is a pretty "high-level" method that is there to just do a simple thing, so you cannot alter at that level if the event if propagated further. The right place to do that is hitTest:withEvent:.
Also have a look at this S.O. answer for more details about this point.
I understand the desired behavior you're looking for Joey - I haven't found something in the API that supports this automatic messaging-up-the-chain behavior with sibling views.
What I originally wrote below was with respect to just informing a parent UIView about a touch. This still applies, but I believe you need to take it a step further and have the parent UIView use the hit testing technique that Sergio described on each of it's subviews that are siblings to the overlay, and have the parent UIView manually invoke a "do something" method on each of it's subviews that pass the hit test. Each of those sibling views can return a BOOL value on whether to abort informing other siblings or continue the chain.
If you find yourself using this pattern a lot, consider adding a category method on UIView that encapsulates the hit testing and asking views to perform a selector.
My Original Answer
With a little bit of manual work, you can wire this together yourself. I've had to do this, and it worked for me, because I had an oft-repeated use case (an overlay view on a button), where it made sense to create some custom classes. If your situation is similar, one of these techniques will suffice.
Option 1:
If the overlay doesn't need to do anything but look pretty, have it opt out of touch handling completely with userInteractionEnabled = NO. This will make it so that the touch event goes to it's parent UIView (the one it is an overlay to).
Option 2:
Have the overlay absorb the touch event (as it would by default), and then invoke a method on the parent UIView indicating that a touch or certain gesture was recognized, and here's what it is. This way, the UIView behind the overlay still gets to act on the touch recognition, even if someone else did the interception.
With Option 2, it's more a fit for simple UIControlEvent types, like UIControlEventTouchDown and UIControlEventTouchUpInside. In my case (a custom UIButton subclass with a custom overlay view on top of it), I'll wire touch down and touch up events on the button to two separate methods. These fire if a touch down or touch up inside event occurs on the button itself. But, they are also hooks I can invoke from the overlay view if I need to simulate that a button press occurred.
Depending on your needs, you could have a known protocol between the overlay and it's parent UIView or just have the overlay test the UIView informally, with a respondsToSelector: check before invoking performSelector: on it with the custom method you want called that would have fired automatically if the UIView wasn't covered by an overlay.

Resources