Search code examples
iosswiftpopoveruimodalpresentationstyleuipopoverpresentationcontroller

adaptivePresentationStyle not Triggering for UIPopoverPresentationControllerDelegate on Compact (iPhone) Devices


I'm attempting to display one UIViewController as a popover from another. For that, I've established the following...

func showPopover(ofViewController popoverViewController: UIViewController, sender: UIView) {
    popoverViewController.modalPresentationStyle = .popover
    popoverViewController.popoverPresentationController?.sourceView = sender
    popoverViewController.popoverPresentationController?.sourceRect = sender.bounds
    popoverViewController.popoverPresentationController?.delegate = self
    self.present(popoverViewController, animated: true, completion: nil)
}

However, the new VC always shows as a full-screen, modal presentation on compact devices, rather than an actual popover. Based on what I've read here & here, that's normal behaviour, but should be customizable through delegation.

I've declared the presenting VC as implementing UIPopoverPresentationControllerDelegate, set it as the delegate, and implemented the required methods; however, the delegation methods are never getting called. This means the 'popover' is still getting shown modally, regardless.

Any advice would be welcome.

Some other callouts:

  • The viewControllerForAdaptivePresentationStyle does get called if an @objc marker is added before it, but that doesn't work for the others.
  • Xcode is giving a warning for each one: Instance method ... nearly matches optional requirement ... of protocol 'UIAdaptivePresentationControllerDelegate'; however, the method signature is a 100% match. Not sure if this is an instance of this bug, which some say still exists in Xcode 10.1.

Thanks.

Delegate functions implemented:

func adaptivePresentationStyle(for: UIPresentationController) -> UIModalPresentationStyle {
    return UIModalPresentationStyle.popover
}

func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
    return UIModalPresentationStyle.popover
}

func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
    switch style {
    case .fullScreen: // Configuration for full-screen
    default: return controller.presentedViewController
    }
}

Solution

  • Thanks to Paulw11 for confirming that the problem was caused by implementing the code in an extension of UIViewController. This was resulting in strange behaviours, whereby the code wasn't being called as usual.

    When migrated to a shared subclass of UIViewController, the following problems were all resolved:

    • The adaptivePresentationStyle method never being called.
    • The viewControllerForAdaptivePresentationStyle method only being called if preceded by the @objc tag.
    • Xcode giving Instance method ... nearly matches optional requirement ... of protocol 'UIAdaptivePresentationControllerDelegate' errors.

    Corrected code is as follows, for anyone seeking the same functionality.

    class CustomViewController: UIViewController {
    
        func showPopover(ofViewController popoverViewController: UIViewController, originView: UIView) {
            popoverViewController.modalPresentationStyle = UIModalPresentationStyle.popover
            if let popoverController = popoverViewController.popoverPresentationController {
                popoverController.delegate = self
                popoverController.sourceView = originView
                popoverController.sourceRect = originView.bounds
                popoverController.backgroundColor = popoverViewController.view.backgroundColor
                popoverController.permittedArrowDirections = UIPopoverArrowDirection.any
            }
            self.present(popoverViewController, animated: true)
        }
    }
    
    extension CustomViewController: UIPopoverPresentationControllerDelegate {
    
        func adaptivePresentationStyle(for: UIPresentationController) -> UIModalPresentationStyle {
            return UIModalPresentationStyle.none
            //return UIModalPresentationStyle.fullScreen
        }
    
        func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
            if traitCollection.horizontalSizeClass == .compact {
                return UIModalPresentationStyle.none
                //return UIModalPresentationStyle.fullScreen
            }
            //return UIModalPresentationStyle.fullScreen
            return UIModalPresentationStyle.none
        }
    
        func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
            switch style {
            case .fullScreen: // Configuration for full-screen
            default:
                return controller.presentedViewController
            }
        }
    }