UIPopoverPresentationController on iOS 8 iPhone
Asked Answered
Y

9

75

Does anyone know if UIPopoverPresentationController can be used to present popovers on iPhones? Wondering if Apple added this feature on iOS 8 in their attempt to create a more unified presentation controllers for iPad and iPhone.

Not sure if its OK to ask/answer questions from Beta. I will remove it in that case.

Yockey answered 14/8, 2014 at 23:31 Comment(0)
E
84

You can override the default adaptive behaviour (UIModalPresentationFullScreen in compact horizontal environment, i.e. iPhone) using the adaptivePresentationStyleForPresentationController: method available through UIPopoverPresentationController.delegate.

UIPresentationController uses this method to ask the new presentation style to use, which in your case, simply returning UIModalPresentationNone will cause the UIPopoverPresentationController to render as a popover instead of fullscreen.

Here's an example of the popover using a segue setup in storyboard from a UIBarButtonItem to "present modally" a UIViewController

class SomeViewController: UIViewController, UIPopoverPresentationControllerDelegate {

    // override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) { // swift < 3.0
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "PopoverSegue" {
            if let controller = segue.destinationViewController as? UIViewController {
                controller.popoverPresentationController.delegate = self
                controller.preferredContentSize = CGSize(width: 320, height: 186)                
            }
        }
    }

    // MARK: UIPopoverPresentationControllerDelegate

    //func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle { // swift < 3.0
    func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
        // Return no adaptive presentation style, use default presentation behaviour
        return .None
    }
}

This trick was mentioned in WWDC 2014 session 214 "View Controller Advancement in iOS8" (36:30)

Entrant answered 4/9, 2014 at 3:16 Comment(10)
This is bugged in the GM for iPhone. If you try to present while the simulator is in portrait, it is always full screen. If you rotate to landscape, it becomes a popover. If you rotate back to portrait again, it stays a popover.Eddie
The solution is to set up the popover BEFORE calling presentViewController. This is exactly the opposite of Apple's example where they explicitly tell you to set up the popover AFTER calling presentViewController.Eddie
Same piece of code in obj-c for those who can't read Swift: https://mcmap.net/q/102657/-uipopviewcontroller-not-workingSubantarctic
@PsychoDad is right. My sense of logic and trust just crumbled. Trust no one, not even WWDC video and Apple class reference.Nonessential
@Nonessential Indeed. Setting it before presenting makes a hell of a lot more sense anyway, and it's possible Apple changed this before the release went out.Eddie
also need set sourceView or barButtonItem for UIPopoverPresentationControllerCorneliuscornell
Help other people out by going to the documentation where Apple incorrectly describes how to configure the popover and click the feedback button and suggest that they correct the code listing.Upcast
To do the same thing using storyboards, see this: https://mcmap.net/q/102658/-uipopoverpresentationcontroller-on-iphone-doesn-39-t-produce-popoverYonita
Does anyone encounter any problem when you rotate your phone with the popover being active?Odoacer
for iOS 8.3+ - per this answer: https://mcmap.net/q/102659/-uimodalpresentationpopover-for-iphone-6-plus-in-landscape-doesn-39-t-display-popover - you need to use the delegate method: adaptivePresentationStyleForPresentationController:traitCollection: to make the popover work in portrait. Also as of right now @Eddie is still right and the apple doc is still wrong.Hydrolyte
C
77

If anybody wants to present a popover with code only, you can use the following approach.

OBJECTIVE - C

Declare a property of UIPopoverPresentationController:

@property(nonatomic,retain)UIPopoverPresentationController *dateTimePopover8;

Use the following method to present the popover from UIButton:

- (IBAction)btnSelectDatePressed:(id)sender
{
    UINavigationController *destNav = [[UINavigationController alloc] initWithRootViewController:dateVC];/*Here dateVC is controller you want to show in popover*/
    dateVC.preferredContentSize = CGSizeMake(280,200);
    destNav.modalPresentationStyle = UIModalPresentationPopover;
    _dateTimePopover8 = destNav.popoverPresentationController;
    _dateTimePopover8.delegate = self;
    _dateTimePopover8.sourceView = self.view;
    _dateTimePopover8.sourceRect = sender.frame;
    destNav.navigationBarHidden = YES;
    [self presentViewController:destNav animated:YES completion:nil];
}

Use the following method to present the popover from UIBarButtonItem:

- (IBAction)btnSelectDatePressed:(id)sender
{
    UINavigationController *destNav = [[UINavigationController alloc] initWithRootViewController:dateVC];/*Here dateVC is controller you want to show in popover*/
    dateVC.preferredContentSize = CGSizeMake(280,200);
    destNav.modalPresentationStyle = UIModalPresentationPopover;
    _dateTimePopover8 = destNav.popoverPresentationController;
    _dateTimePopover8.delegate = self;
    _dateTimePopover8.sourceView = self.view;
     CGRect frame = [[sender valueForKey:@"view"] frame];
    frame.origin.y = frame.origin.y+20;
    _dateTimePopover8.sourceRect = frame;
    destNav.navigationBarHidden = YES;
    [self presentViewController:destNav animated:YES completion:nil];
}

Implement this delegate method too in your view controller:

- (UIModalPresentationStyle) adaptivePresentationStyleForPresentationController: (UIPresentationController * ) controller {
    return UIModalPresentationNone;
}

To dismiss this popover, simply dismiss the view controller. Below is the code to dismiss the view controller:

-(void)hideIOS8PopOver
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

SWIFT

Use the following method to present the popover from UIButon:

func filterBooks(sender: UIButon)
    {
        let filterVC =  FilterDistanceViewController(nibName: "FilterDistanceViewController", bundle: nil)
        var filterDistanceViewController = UINavigationController(rootViewController: filterVC)
        filterDistanceViewController.preferredContentSize = CGSizeMake(300, 205)
        let popoverPresentationViewController = filterDistanceViewController.popoverPresentationController
        popoverPresentationViewController?.permittedArrowDirections = .Any
        popoverPresentationViewController?.delegate = self
        popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
        popoverPresentationViewController!.sourceView = self.view;
        popoverPresentationViewController!.sourceRect = sender.frame

        filterDistanceViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
        filterDistanceViewController.navigationBarHidden = true
        self.presentViewController(filterDistanceViewController, animated: true, completion: nil)
    }

Use the following method to present the popover from UIBarButtonItem:

func filterBooks(sender: UIBarButtonItem)
    {
        let filterVC =  FilterDistanceViewController(nibName: "FilterDistanceViewController", bundle: nil)
        var filterDistanceViewController = UINavigationController(rootViewController: filterVC)
        filterDistanceViewController.preferredContentSize = CGSizeMake(300, 205)
        let popoverPresentationViewController = filterDistanceViewController.popoverPresentationController
        popoverPresentationViewController?.permittedArrowDirections = .Any
        popoverPresentationViewController?.delegate = self
        popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
        popoverPresentationViewController!.sourceView = self.view;
        var frame:CGRect = sender.valueForKey("view")!.frame
        frame.origin.y = frame.origin.y+20
        popoverPresentationViewController!.sourceRect = frame

        filterDistanceViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
        filterDistanceViewController.navigationBarHidden = true
        self.presentViewController(filterDistanceViewController, animated: true, completion: nil)
    }

Implement this delegate method too in your view controller:

func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle{
        return .None
    }

Please make sure to add delegate UIPopoverPresentationControllerDelegate in .h/.m/.swift file

Cockleshell answered 15/11, 2014 at 8:28 Comment(8)
This worked well for me on iPhone in landscape mode iOS8/SwiftVhf
I don't think UIPopoverPresentationController needs to be declared. It worked for me when I simply access it and set its property through the destination view controller. I'm using Swift and Xcode 6.2.Nonessential
destNav.popoverPresentationController is nil.Auvergne
If you've had this problem, help other people out by going to the documentation where Apple incorrectly describes how to configure the popover and click the feedback button and suggest that they correct the code listing.Upcast
OMG, Apple's own documentation mentioned everything you wrote up except including the adaptivePresentationStyleForPresentationController: method. Nothing worked until I included that method. Thank you!!!Neuromuscular
Not working for my case: dateVC is initialized without StoryboardAnselme
@Andy move these lines filterDistanceViewController.modalPresentationStyle = UIModalPresentationStyle.Popover filterDistanceViewController.navigationBarHidden = true to be right before let popoverPresentationViewController will solve the problemVip
@DesertRose, were you able to dismiss the popover by tapping outside, on a regular sized iPhone? Even with the method above, I am seeing that on a non-plus sized phone, it will dismiss if I tap inside, but not outside. (#50262923). Thanks.Locksmith
T
12

PROBLEM: iPhone popover displays fullscreen and does not respect preferredContentSize value.

SOLUTION: Contrary to what Apple suggests in the UIPopoverPresentationController Class reference, presenting the view controller after getting a reference to the popover presentation controller and configuring it.

// Get the popover presentation controller and configure it.
//...

// Present the view controller using the popover style.
[self presentViewController:myPopoverViewController animated: YES completion: nil]; 
Tomchay answered 18/1, 2016 at 10:58 Comment(2)
same thing here, implementing the delegate and all wasn't enough, presenting after configuring did the trick on iPhone 6S iOS 10.1Hostile
its essential only to set delegate before presentViewController,Verrazano
K
4

Make sure to implement UIAdaptivePresentationControllerDelegate

like this:

- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
    return UIModalPresentationNone;
}

If you don't want full-screen popovers

Kleenex answered 12/8, 2015 at 12:41 Comment(0)
C
2

I've found some workaround.

On Xcode6.1, use presentationController.delegate instead of popoverPresentationController.delegate.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier compare:@"showPopOver"] == NSOrderedSame) {
        UINavigationController * nvc = segue.destinationViewController;
        UIPresentationController * pc = nvc.presentationController;
        pc.delegate = self;
    }
}

#pragma mark == UIPopoverPresentationControllerDelegate ==
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller
{
    return UIModalPresentationNone;
}

In WWDC 2014 "View Controller Advancements in iOS8", below codes can show popover on iPhone.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{

    UINavigationController * nvc = segue.destinationViewController;
    UIPopoverPresentationController * pvc = nvc.popoverPresentationController;
    pvc.delegate = self;
}

#pragma mark == UIPopoverPresentationControllerDelegate ==
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller
{
    return UIModalPresentationNone;
}

But On Xcode 6.1, these codes shows FullScreen presentation... (nvc.popoverPresentationController is nil)

I doubt it might be an Apple's bug.

Controvert answered 20/12, 2014 at 9:15 Comment(0)
C
1

In iOS 8.3 and later, use the following syntax in the UIPopoverPresentationControllerDelegate protocol to override your popup's UIModalPresentationStyle.

func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
    return .none
}
Cryoscope answered 7/12, 2016 at 21:58 Comment(0)
M
1

You can extend the UIPopoverPresentationControllerDelegate like this:

protocol PopoverPresentationSourceView {}
extension UIBarButtonItem : PopoverPresentationSourceView {}
extension UIView : PopoverPresentationSourceView {}

extension UIPopoverPresentationControllerDelegate where Self : UIViewController {
   func present(popover: UIViewController, 
        from sourceView: PopoverPresentationSourceView, 
        size: CGSize, arrowDirection: UIPopoverArrowDirection) {

      popover.modalPresentationStyle = .popover
      popover.preferredContentSize = size
      let popoverController = popover.popoverPresentationController
      popoverController?.delegate = self
      if let aView = sourceView as? UIView {
          popoverController?.sourceView = aView
          popoverController?.sourceRect = CGRect(x: aView.bounds.midX, y: aView.bounds.midY, width: 0, height: 0)
      } else if let barButtonItem = sourceView as? UIBarButtonItem {
          popoverController?.barButtonItem = barButtonItem
      }
      popoverController?.permittedArrowDirections = arrowDirection
      present(popover, animated: true, completion: nil)
   }
}

You can now call present(popover: from: size: arrowDirection: ) from any view controller that implements UIPopoverPresentationControllerDelegate eg.

class YourViewController : UIViewController {
    @IBAction func someButtonPressed(_ sender: UIButton) {
        let popover = SomeViewController()
        present(popover: popover, from: sender, size: CGSize(width: 280, height: 400), arrowDirection: .right)
    }
}

extension YourViewController : UIPopoverPresentationControllerDelegate {
    func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
        return .none
    }
}
Mannikin answered 6/6, 2020 at 3:56 Comment(0)
G
0

add these two methods in your WEBVIEW class. and add

-(void) prepareForSegue: (UIStoryboardSegue * ) segue sender: (id) sender {
    // Assuming you've hooked this all up in a Storyboard with a popover presentation style
    if ([segue.identifier isEqualToString: @"showPopover"]) {
        UINavigationController * destNav = segue.destinationViewController;
        pop = destNav.viewControllers.firstObject;
        // This is the important part
        UIPopoverPresentationController * popPC = destNav.popoverPresentationController;
        popPC.delegate = self;
    }
}

- (UIModalPresentationStyle) adaptivePresentationStyleForPresentationController: (UIPresentationController * ) controller {
    return UIModalPresentationNone;
}
Garden answered 7/10, 2014 at 10:59 Comment(0)
L
0

In the UIAdaptivePresentationControllerDelegate you must use this method:

func adaptivePresentationStyle(for: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle

instead of this:

func adaptivePresentationStyle(for: UIPresentationController) -> UIModalPresentationStyle
Libidinous answered 11/4, 2022 at 7:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.