I'm trying to present a view controller modally with a custom presenter using UIPresentationController (and UIViewControllerTransitioningDelegate).
The problem is that the transitioning delegate is being deinitialized immediately after animationController(presented:presenting:source:) is called. This means animationController(dismissed:) never gets called - and thus, a dismissal animation cannot be set.
In the end, I want to be able to define the dismissal animation. I believe what I explained above is the root of the problem, but can't find anything about this online.
Here is my implementation of UIViewControllerTransitioningDelegate:
final class Manager: NSObject, UIViewControllerTransitioningDelegate {
private let size: CGSize
var animator: Animator
init(size: CGSize) {
self.size = size
self.animator = Animator(duration: 0.4, loaf: loaf)
}
deinit {
print("DEINIT") // 2) Then this is being called immediately after
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return Controller(
presentedViewController: presented,
presenting: presenting,
size: size
)
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
animator.presenting = true
return animator // 1) This is called first after the view controller is presented
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
animator.presenting = false
return animator // 3) This is never called
}
}
And this is how I'm setting the transitioning delegate:
extension UIViewController {
func presentModally(_ viewController: UIViewController, size: CGSize) {
viewController.transitioningDelegate = Manager(size: size)
viewController.modalPresentationStyle = .custom
present(viewController, animated: true)
}
}
When the view controller is then dismissed, the view always defaults to being pushed down and disappearing. Again, animationController(dismissed:) is never called and I can't figure out why.
I was able to fix this by storing a reference to the UIViewControllerTransitioningDelegate on the presenting view controller. Then, when presenting the modal, set it like this:
extension UIViewController {
func presentModally(_ viewController: UIViewController, size: CGSize) {
viewController.transDelegate = Manager(size: size)
viewController.transitioningDelegate = viewController.transDelegate
viewController.modalPresentationStyle = .custom
present(viewController, animated: true)
}
}
Related
For example i have a custom transitioning delegate with only 1 method implemented:
class CustomTransition: NSObject, UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let presentationController = CustomTransitionPresentationController(presentedViewController: presented, presenting: presenting)
return presentationController
}
}
and then has set only modalPresentationStyle = .custom of a view controller and transitioningDelegate = ourCustomTransitionWithoutAnyAnimator
So what type of animation will be used?
Unless you specify the Delegate to conform for ourCustomTransitionWithoutAnyAnimator inside the viewcontroller, otherwise it will fall into the case below:
It will use whatever the presentingViewController conforming UIViewControllerTransitioningDelegate and return its own custom presenting animations
as in your case it returns: CustomTransitionPresentationController.
Your CustomTransitionPresentationController could be conforming UIViewControllerAnimatedTransitioning and have the following methods for customisations:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {}
Or a subclass of UIPresentationController
How can I get a presenting modal to be of custom size? Tried lots of different solutions, many which seem obsolete
This is how I instantiate the modal view from the parent view controller:
self.definesPresentationContext = true
let vc = (storyboard?.instantiateViewController(withIdentifier: "modalViewController"))!
vc.modalPresentationStyle = .overCurrentContext
vc.preferredContentSize = CGSize(width: 100, height: 100)
present(vc, animated: true, completion: nil)
But, the modal view covers the full screen instead of just occupying 100 * 100.
You need to implement UIViewControllerTransitioningDelegate methods and UIViewControllerAnimatedTransitioning methods for customizing the presented UIViewController size.
To know how to implement custom animator,
Refer to: https://github.com/pgpt10/Custom-Animator
Edit:
class ViewController: UIViewController
{
//MARK: Private Properties
fileprivate let animator = Animator()
//MARK: View Lifecycle Methods
override func viewDidLoad()
{
super.viewDidLoad()
}
override func awakeFromNib()
{
super.awakeFromNib()
self.transitioningDelegate = self
self.modalPresentationStyle = .custom
}
//MARK: Button Action Methods
#IBAction func dismissController(_ sender: UIButton)
{
self.dismiss(animated: true, completion: nil)
}
}
// MARK: - UIViewControllerTransitioningDelegate Methods
extension ViewController : UIViewControllerTransitioningDelegate
{
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
self.animator.transitionType = .zoom
self.animator.size = CGSize(width: 100, height: 100)
return self.animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
return self.animator
}
}
I'm trying to present a ViewController (embedded in a NavigationController) from a button inside a TableViewController. The presented ViewController should be half the height of the TableViewController. I've tried with the following code below but it doesn't seem to work (Swift 3). Can someone kindly help? thanks!
class AddNewRecipeTableViewController: UITableViewController, UIViewControllerTransitioningDelegate {
#IBAction func popUpTest(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = storyboard.instantiateViewController(withIdentifier: "popUpTest") as! UINavigationController
pvc.modalPresentationStyle = UIModalPresentationStyle.custom
pvc.transitioningDelegate = self
self.present(pvc, animated: true, completion: nil)
}
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController!, sourceViewController source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)
}
}
class HalfSizePresentationController : UIPresentationController {
override var frameOfPresentedViewInContainerView : CGRect {
return CGRect(x: 0, y: 0, width: containerView!.bounds.width, height: containerView!.bounds.height/2)
}
}
You have:
func presentationControllerForPresentedViewController(
presented: UIViewController,
presentingViewController presenting: UIViewController!,
sourceViewController source: UIViewController)
-> UIPresentationController? {
That method will never be called, because in Swift 3 it doesn't correspond to any method that Cocoa knows about. (I'm suprised you don't report getting a warning from the compiler about this.)
You probably meant to implement presentationController(forPresented:presenting:source:), like this:
func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController)
-> UIPresentationController? {
But even that won't be called, because you have not set the presented view controller's modalPresentationStyle to .custom.
This is my extension:
extension UIViewController: UIViewControllerTransitioningDelegate {
func presentAssignBookToClassesViewController(controller: BWAssignBookToClassesViewController) {
controller.modalPresentationStyle = .Custom
controller.transitioningDelegate = self
controller.preferredContentSize = CGSizeMake(500, 575)
presentViewController(controller, animated: true, completion: nil)
}
func presentSettingsStoryboard() {
if let settingsController = UIStoryboard(name: "TeacherSettingsStoryboard", bundle: nil).instantiateInitialViewController() {
settingsController.modalPresentationStyle = .Custom
settingsController.transitioningDelegate = self
settingsController.preferredContentSize = CGSizeMake(500, 575)
presentViewController(settingsController, animated: true, completion: nil)
}
}
//MARK: - UIViewControllerTransitioningDelegate
public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
}
Within presentationControllerForPresentedViewController: I need to return either BWOverlayPresentationController or BWSettingsPresentationController depending on what method was called. How to achieve this?
You can simply distinguish them via restorationIdentifier (you can set this simply using storyboard):
//MARK: - UIViewControllerTransitioningDelegate
public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
if presented.restorationIdentifier == BWSettingsRestorationIdentifier {
return BWSettingsPresentationController(presentedViewController: presented, presentingViewController: presenting)
} else {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
}
I would suggest you can create one BaseViewContoller with two viewController objects eg: BWOverlayPresentationController, BWSettingsPresentationController and based on condition you can return the the specific view controller.
public func presentationControllerForPresentedViewController(presented: UIViewController?, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
let viewController = BaseViewController()
if (viewController.(somePropertyInViewController)) {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
else {
return BWSettingsPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
public func presentationControllerForPresentedViewController(presented: UIViewController?, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
// You can create some property in presented/presenting viewController.
// and check here to return specific viewContoller.
if (presented.(somePropertyInViewController)) {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
else {
return BWSettingsPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
}
How do I determine when the parent view controller has been hidden or shown when I use the UIModalPresentationOverCurrentContext modal presentation style? On normal situations I can use the viewWillAppear: and viewWillDisappear:, but they seem not to be firing on this.
UIModalPresentationOverCurrentContext is intended to be used to present the content over your current viewController. What that means is, if you have animation or view changes inside your parentViewController, you can still see through the childViewController if the view is transparent. So, it also means that view never disappears for view over current context. It seems legit that viewWillAppear:, viewDidAppear:, viewWillDisappear: and viewDidDisappear do not get called.
You can however use UIModalPresentationStyle.Custom to have exact same behavior to present over current context. You wont receive view appearance callbacks but you can create your own custom UIPresentationController to get those callbacks.
Here is an example implementation,
class MyFirstViewController: UIViewController {
....
func presentNextViewController() {
let myNextViewController = MyNextViewController()
myNextViewController.modalPresentationStyle = UIModalPresentationStyle.Custom
myNextViewController.transitioningDelegate = self
presentViewController(myNextViewController, animated: true) { _ in
}
}
...
}
extension MyFirstViewController: UIViewControllerTransitioningDelegate {
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
{
let customPresentationController = MyCustomPresentationController(presentedViewController: presented, presentingViewController: presenting)
customPresentationController.appearanceDelegate = self
return customPresentationController
}
}
extension MyFirstViewController: MyCustomApprearanceDelegate {
func customPresentationTransitionWillBegin() {
print("presentationWillBegin")
}
func customPresentationTransitionDidEnd() {
print("presentationDidEnd")
}
func customPresentationDismissalWillBegin() {
print("dismissalWillBegin")
}
func customPresentationDismissalDidEnd() {
print("dismissalDidEnd")
}
}
protocol MyCustomApprearanceDelegate: class {
func customPresentationTransitionWillBegin()
func customPresentationTransitionDidEnd()
func customPresentationDismissalWillBegin()
func customPresentationDismissalDidEnd()
}
class MyCustomPresentationController: UIPresentationController {
weak var appearanceDelegate: MyCustomApprearanceDelegate!
override init(presentedViewController: UIViewController, presentingViewController: UIViewController) {
super.init(presentedViewController: presentedViewController, presentingViewController: presentingViewController)
}
override func presentationTransitionWillBegin() {
appearanceDelegate.customPresentationTransitionWillBegin()
}
override func presentationTransitionDidEnd(completed: Bool) {
appearanceDelegate.customPresentationTransitionDidEnd()
}
override func dismissalTransitionWillBegin() {
appearanceDelegate.customPresentationDismissalWillBegin()
}
override func dismissalTransitionDidEnd(completed: Bool) {
appearanceDelegate.customPresentationDismissalDidEnd()
}
}