Present view controller while touching - ios

I want to implement a hold-to-preview button that brings up a view containing an AVPlayerLayer, which plays as long as the touch doesn't end. The video player is contained in a different view controller, and I am hoping to be able to use presentViewController:animated: when presenting it, and not just add it as a subview and child view controller.
My question is about how to deal with the touch event. I see two possible ways:
I try to transfer the active touch down event to the presented view controller (not sure if even possible), or
I try to keep the original view controller's gesture recognizer active, and then let the video view controller know when it's time to dismiss itself. I'm hoping this could be achieved either by just setting the presented view controller's userInteractionEnabled to false, or perhaps using a UIViewControllerTransitioningDelegate to present it, and then just skip calling completeTransition: or something similar (I believe touches don't register on the new view until you complete the animation, but please correct me if I'm wrong).

My question is about how to deal with the touch event.
Touches are always associated with the view that they start in. You can't transfer the touch to a different view. I've never tried it, but the options I think you should explore first are:
Use view controller containment. Make your preview view controller a child view controller of the one where the touch originates. That way the parent and its view hierarchy never go away, although they could be covered up.
Attach the gesture recognizer to the window. A window is a view, and should be able to have gesture recognizers. You could make the gesture recognizer's target the app delegate or some other object that will always be around, and have the delegate post a notification when the recognizer is triggered. Again, I haven't tried this, but it seems like it should work.

Related

UIPresentationController multiple sizes or detents

I am working on trying to implement a presentation controller that essentially mimics the behavior of the UISheetPresentationController. I am doing this because I want to add a third detent and also I want this to work for iOS 14, so I cannot use the existing UISheetPresentationController.
I have it where I can present a view controller and it will stop at one location (say 50% of the screen height). And then I have a pan gesture that, when active, starts the interactive dismissal of the view controller. If the view controller is pulled down so far when the gesture ends, the view is dismissed, otherwise the dismissal is cancelled and the view returns to the 50% height.
That is all working fine. But then how do I add multiple stops, or detents? The pan gesture starts an interactive dismissal, but that dismissal does not support multiple different places where the view controller could end up.
Does anyone know how Apple implements their "detents" in the UISheetPresentationController? Or does anyone have any suggestions on how this might be implemented?
You can also add this to your .sheet{}
.presentationDetents([.medium, .large, .fraction(0.1)])
or
.presentationDetents([.medium, .large, .height(300)])

Preventing unhandled touch events on a child view controller from passing through to container view

I have a container view controller managing its own full-screen content view, with several gesture recognizers attached. A child view controller can be overlaid over a portion of the screen; its root view is a UIView providing the opaque background color, which is covered by a UIScrollView, which in turn contains a complex view hierarchy of stack views, etc.
Scrolling in the child works correctly, as well as any user interactions with its subviews. The problem I'm having is that any taps or other non-scrolling gestures on the the scroll view itself (i.e. not inside any of its subviews) fall through the empty UIView behind it and are unexpectedly handled by the gesture recognizers on the root view of the parent (container) controller. I want those touches to be swallowed up by the child's background view so that they are ignored/cancelled.
My first thought was to override nextResponder on the child VC to return nil, assuming that would prevent touch events from passing to the superview. No success there, so I tried overriding the touch handling methods (touchesBegan: etc.) on the child controller, but they never get called. Then I substituted a simple UIView subclass to be the root view of my child controller, likewise trying both of those approaches there instead. Again returning nil for nextResponder has no effect, and the touch methods never get called.
My responder chain looks to be set up exactly as I would expect: scroll view --> child VC's root view --> child VC --> parent's root view --> parent VC. That makes me think my controller containment is set up correctly, and makes me suspect that the gesture recognizers on the parent's root view are somehow winning out over the responder chain in a way that I don't understand.
This seems like it should be easy. What am I missing? Thanks!
I think I understand better what's going on here thanks to this very helpful WWDC video.
Given an incoming touch, first the system associates that touch with the deepest hit-tested view; in my case that's the UIScrollView. Then it apparently walks back up the hierarchy of superviews looking for any other attached recognizers. This behavior is implied by this key bit of documentation:
A gesture recognizer operates on touches hit-tested to a specific view and all of that view’s subviews.
The scroll view has its own internal pan recognizer(s), which either cancel unrecognized touches or possibly fall back on responder methods that don't happen to forward touches up the responder chain. That explains why my responder methods never get called, even when my own recognizers are disabled.
Armed with this information, I can think of a few possible ways to solve my problem, such as:
Use gesture delegate methods to ignore touches if/when the associated view is under a child controller.
Write a "null" gesture recognizer subclass that captures all touches and ignores them, and attach that to the root view of the child controller.
But what I ended up doing was simply to rearrange my view hierarchy with a new empty view at the top, so that my child controller views can be siblings of the main content view rather than its subviews.
So the view hierarchy changes from this:
to this:
This solves my problem: my gesture recognizers no longer interact with touches that are hit-tested to the child controller's views. And I think it better captures the conceptual relationships between my app's controllers, without requiring any additional logic.

Detecting shake gesture by multiple view controllers

I need to detect shake gesture in iOS. I have done the usual stuff and it works perfectly fine. The thing is I have multiple view controllers in UITabBarController and I wish each of them to detect the shake gesture.
When shaking in any of the view controller , I get switched to a particular tab. The problem is if I shake in one view controller and try to shake in other controller the gesture is not detected unless some action is performed in that controller.
I know I need to set becomeFirstResponder but I need to know how can this property be set to the current tab of the UITabBarController so that the shake gesture is recognised by all tabs.
Write the code for detection (usually via a notification observer for shake) in a base view controller and and all the controller will subclass from this. Now you can write the code to move the particular tab in this base controller.
Problem solved.

Forwarding touches to UINavigationController Interactive Pop Gesture Not completely Working

I'm trying to enable interactive pop gesture recogniser on my keyboard's accessory view. It does not work by default.
I passed an interactive pop gesture recogniser reference to my accessory view in order to forward its touch events to the recogniser
It particularly works: the navigation bar's title gets changed and the background of the accessory view reveals the previous view controller's view as if the transition did start. But the top view itself remains in place even if the gesture recogniser completes tracking.
I also tried to forward touch events to the navigation controller itself, to its view, to its top view controller and to their window. Nothing changed even after forwarding to all of them simultaneously
Any ideas what is missing?
It looks like it is not possible to reuse touch event instances in the responder chain. Once the sendEvent: on UIWindow is called, there is already a certain view owning the touch, so there is no cense in forwarding the UIEvent instances to other views or their gesture recognisers.
However, the owning view can forward events to its nextResponder()s (e.g.: one of the gesture recognisers attached to this view or to one of the subviews of the view)
The only chance to forward touches to another view (from another view hierarchy) or another view's gesture recognisers is before the UITouch object creation: i.e. on the UIWindow level during the hitTest:withEvent: method invocation, which calls the pointInside:withEvent: method
Anyway I'm not sure whether it is possible to forward touches from one UIWindow to another. Will update the answer later
http://www.russbishop.net/uitouchtypestylus?utm_campaign=iOS%2BDev%2BWeekly&utm_medium=email&utm_source=iOS_Dev_Weekly_Issue_225
http://smnh.me/hit-testing-in-ios/

How to get a parent view to recognize rotation in subview ios

I have two viewcontrollers. When the user clicks a button, the parent vc uses presentviewcontroller to call the subview. The problem I am having is when the user rotates the iphone in the subview then closes it, the parent view does not fit the whole screen (it turns to landscape but only displays on half of the screen). Is there a way to let the parent vc know it needs to rotate when it happens in a subview?
Also note that the parent view rotates perfectly when the rotation takes place in its view.
Your problem may be related to this previous [question][1]: View rotation notifications: why didRotateFromInterfaceOrientation: doesn't get called?
But I am also a bit unclear about your terminology. I think you're using sub-view to refer to a subordinate VC, rather than a subordinate in the View Hierarchy.
Since you have two VCs you need to make sure that rotation handling code is present for both, and that the handlers are receiving the notifications. I suggest putting in diagnostic code in the rotation handlers for both VCs to see which is being called, and when - that should give you enough information to solve the problem or at least to post a followup.

Resources