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)])
Related
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.
I am attempting to create a custom view controller container, which will display a drawer at the bottom of the screen, like the Apple mail or music apps, and I want the user to either be able to tap on it to transition it to fullscreen, or slide it up interactively to reveal the content.
I have the drawer working, using a UIPanGestureRecognizer to slide it.
I can implement this by adding the content controller as a child controller, the content view to the hierarchy and call viewWillAppear: and viewDidAppear: when appropriate.
But I wish to allow the content view controller to animate alongside the swipe (e.g. any animations in viewWillAppear:, like with interactive pop), thus I am looking at custom modal presentation and UIPercentDrivenInteractiveTransition, but I am hitting a wall, and I can't see why this is happening. I have setup a transitioning delegate, returning a custom animation controllers and an interaction controller which is a UIPercentDrivenInteractiveTransition object.
My drawer is part of the container controller's view hierarchy, and naturally I want the content controller's view to be a subview of the drawer. But when calling presentViewController:animated:completion:, a new UITransitionViewsubview is added to the UIWindow, supposedly for where the transition animation should occur. But this kills my UIPanGestureRecognizer and the user cannot perform the swipe to open the drawer.
I tried creating a custom UIPresentationController and other ways to control where in the hierarchy this containerView should be, but I am unable to change the behavior.
Is what I am attempting to do the correct way? What have I missed?
If anyone is interested, here is my framework: LNPopupController
Update 2015.11.19
A demo project as well
Obj-C -> https://github.com/saiday/DraggableViewControllerDemo
Swift 2.x -> https://github.com/ostatnicky/DraggableViewController
Swift 4.x -> https://github.com/satishVekariya/DraggableViewController
Thanks #avdyushin mentioned my blog post.
And yes, my post Draggable view controller? Interactive view controller! is a tutorial just about your topic.
Quoted from my blog post:
How to do it
Design philosophy
There is no two UIViewController in together, instead there's only one UIViewController at a time. When user begin drag on certain subview, present another view controller with custom UIViewControllerAnimatedTransitioning by custom UIPercentDrivenInteractiveTransition protocol.
Technical things
These two protocols is the foundation of customize interactive UIViewController transitions. In case you don't know yet, take a glance before starting.
1. UIViewControllerAnimatedTransitioning protocol
2. UIPercentDrivenInteractiveTransition protocol
An updated answer for iOS 10: this is now trivial using UIViewPropertyAnimator.
UIViewPropertyAnimator is a continuation of the block-based animation concept, taken to its logical continuation - an object oriented wrapper around animations. This allows for much more control, such as controlling the completion of the animation using fractionComplete - precisely what was needed here.
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.
Note: I'm not talking about custom view controller transition effects which can be done by using a custom view controllers it's the iOS 5+ API.
I'm talking about transitioning to another view controller, where a view from the presently displayed view controller is animated to the view controller to be presented's view.
EXAMPLE
-you have friendsViewController which displays a list of the current users friends. Each table view cell has a profile picture and name.
-click on a cell, all other cells fade away and the name and picture animate to the top. At this point, UserProfileViewComtroller is displayed.
THEORIES
-I could easily do this by combining the two view controllers, but UserProfileViewComtroller can be launched from other parts of the app.
-if the UserProfileViewControllers view is instantiated, I could convert the coordinates using UIViews methods
I feel like there is a more appropriate/cleaner solution here which is why I'm asking the community for help :)
It seems to me that what you want is exactly about view controllers transition, since you want to do 'something' that would look to the user as if you took a view from old VC and moved it to the new VC.
Then you're in luck, as you're allowed to move a UIView from one view controller to another using [superview addSubview:view] as part of the transition you want to do.
This can be done on any iOS version, although it's easier now as in iOS 7 there's a delegate you write (see <UIViewControllerAnimatedTransitioning> reference) which has access to both VC's view hierarchies and can change them at will (move one view, fade other views) during transition period.
Also, making your new view controller during the transition transparent (or using old controller's snapshot) will help you hide the fact that VC changed.
Not so much an answer but a technique that might inspire a solution. I did an app that had need for a custom transition like this. The original app arranged itself then took a snapshot, so at the last moment the user is looking at an image. The second viewController was created, given coordinates etc, and the image, then shown immediately. It put the image into its view (subview with same bounds).
At this point the second vc has complete control, and can fade in some other content etc. the reverse was more or less as the start - the image is used, swapped, used removed to uncover the real view content.
Note that this took a bit of time to get it working with no glitches etc.
EDIT: if you are concerned in turning the whole original view into an image, then modify the technique. For instance, in the original view, fade all other content to black but the cell, then snapshot the one cell. The second view will start with an all black background, and place the cell image over top it, then go from there.
EDIT2: As mentioned in the comments, you of course push the second view with no animation, so it happens instantaneously. By setting a small image on the second vc, with an agreed upon background, you can quickly "pass the baton" so to speak and let the second controller go to work quickly and seamlessly.
I have a uitableview controller which is a subview to a view managed by a uiviewcontroller. nothing really out of the ordinary but the tableview tracks gestures on the wrong axis(only on device).
Basically you scroll up/down table doesnt do anything, and left/right scrolls table up/down. its super weird. i was hoping somebody has seen this before and maybe know what causes it?
Edit: heres a video
http://c.drunknbass.com/EB7m
at the end i am scrolling a uiscrollview that scrolls normally and is a child of the same uiviewcontroller.view
UIKit relies on there being a key window, and that window having a root view controller, to be able to correctly handle events, and forward them to your code. I suspect that perhaps one of those things is not set up correctly in your app. (Such that the device orientation isn't matching up with the visual orientation of your UI.)
Also note that prior to iOS 5, making one controller's view the child of another controller wasn't really supported by UIKit. It can be done, and mostly works, but you are going to have to manage the forwarding of all of your lifecycle events. (See the notes on controller containment in the docs, and the description of -automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers as well.)