33

I'm trying to implement the new UIPopoverPresentationController in my iPhone app (using Objective C). What I want is a simple popover with a tableview that emanates from the initiating button.

--Edit--

Here's my REVISED code, adapted from research in the docs, SO, and from input in comments below:

- (IBAction)selectCategoryBtn:(UIButton *)sender
{
    [self performSegueWithIdentifier:@"CatSelectSegue" sender:self.selCatButton];
}

-(void) prepareForSegue:(UIStoryboardSegue *) segue Sender:(id) sender
{
    if (sender == self.selCatButton)
    {
        if ([segue.identifier isEqualToString:@"CatSelectSegue"])
        {
            UIPopoverPresentationController *controller = segue.destinationViewController;
            controller.delegate = self;
            controller.sourceView = self.selCatButton;
            controller.sourceRect = self.selCatButton.frame;
        }
    }
}


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

Here's my storyboard hookup:

enter image description here

However this simply presents a tableview in a modal fashion, rising up from the bottom and consuming the entire screen.

I've googled, and looked all over SO, but it appears that I'm not the only one confused by what I'd hoped would resolve a nettlesome issue for the iPhone.

Can anyone see a glitch in my code or direct me to a clear tutorial? I've looked, but maybe the API is just so new nobody's got a handle on it yet.

Thanks!

2nd edit:

Here's what gets presented as a result of the code above. I reduced the size of the tableview in the View Controller I expected to be presented as a popover. I colored the background gray, just to clarify what's showing up instead of the popover.

enter image description here

rattletrap99
  • 1,469
  • 2
  • 17
  • 36
  • Your code looks OK; `UIModalPresentationNone` should stop the adaptivity. Have you declared that your class conforms to the `UIAdaptivePresentationControllerDelegate`? Is `adaptivePresentationStyleForPresentationController:` being called? – Robotic Cat Feb 15 '15 at 00:23
  • No, and this is perhaps where things are breaking down for me. I'm calling that delegate method, but I thought it was part of UIPopoverPresentationControllerDelegate, to which my VC does conform. I got no errors without the UIAdaptivePresentationControllerDelegate declaration. I find this very confusing. – rattletrap99 Feb 15 '15 at 00:32
  • And I need to clarify something else: There are two View Controllers here--The one containing the initiating button, and the one containing the tableview. At risk of seeming stupid, which VC should be declared as the UIAdaptivePresentationControllerDelegate? – rattletrap99 Feb 15 '15 at 00:40
  • Normally I would expect this to be the VC that contains the `UIBarButtonItem` that triggers the segue to display the popover. This VC should implement the `adaptivePresentationStyleForPresentationController:` method and also be set as the popover's delegate. It should also be declared as conforming to `UIAdaptivePresentationControllerDelegate`. – Robotic Cat Feb 15 '15 at 00:48
  • OK, if I understand correctly, this should be in my VC.h that holds the initiating button: @interface AddTransactionVC : UIViewController ? – rattletrap99 Feb 15 '15 at 00:59
  • Incidentally, I'm using a standard button rather than a BarButton, if that's relevant... – rattletrap99 Feb 15 '15 at 01:03
  • 1
    UIPopoverController has never worked on iPhone. (unless its been added recently, perhaps it does on iPhone6+, Ive found UIModalSheet presentation works on that) Does this work as expected on iPad? if so then there is nothing wrong with your code, UIKit is choosing what it deems to be a more appropriate presentation style for the device. If you really want a popover you'll need to grab an open source one (Ive used WEPopoverController a few times, works well) or roll your own. – Jef Feb 15 '15 at 01:23
  • This isn't UIPopoverController, it's UIPopoverPresentationController, a new class in iOS 8. I'm trying to use it since it's supposed to eliminate the need for 3rd party libraries. However promising it may seem, as you can see, it's challenging, at least to me. :) – rattletrap99 Feb 15 '15 at 01:28
  • @rattletrap99: Your `@interface` declaration looks good. Calling from a standard button should be OK although I've not tried it. How big is the tableview you are presenting? Is it the contentSize too big for a small popover? – Robotic Cat Feb 15 '15 at 03:01
  • I just squished the TV down, but the real problem seems to be that prepareForSegue is never being called. See revised code in edit to question... – rattletrap99 Feb 15 '15 at 03:11
  • RoboticCat--please see my comment below @jef's answer. Thanks for trying! – rattletrap99 Feb 15 '15 at 18:51
  • @rattletrap99: I've added an answer that shows you how to do this using the `UIPopoverPresentationController`. @Jef might think this cannot be done but his answer is factually incorrect. – Robotic Cat Feb 15 '15 at 21:18
  • For me the problem was that, calling the PopoverViewController through a "PerformSegue" was always leaving it Full Screen. Deleting the storyboard segue and manually assembling it and showing through presentViewController solved it. – Alexandre Feb 11 '18 at 16:54

5 Answers5

78

Steps:

A) Link your UIButton to the popover's view controller using the Present As Popover segue type. I actually had to create a new project to get this to appear but it's probably something to do with the base SDK.

B) Make the View Controller containing the UIButton conform to the <UIPopoverPresentationControllerDelegate>. E.g. In your MyViewController.m file add:

@interface MyViewController () <UIPopoverPresentationControllerDelegate>

C) Add the method below to the View Controller containing the UIButton:

- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {

    return UIModalPresentationNone;
}

D) Add the following into your prepareForSegue:sender: replacing your segue.identifier check:

if ([segue.identifier isEqualToString:@"CatSelectSegue"]) {
    UIViewController *dvc = segue.destinationViewController;
    UIPopoverPresentationController *controller = dvc.popoverPresentationController;
    if (controller) {
        controller.delegate = self;
    }
}

Code tested and proof it works:

Popover on iPhone without 3rd Party Controls

Edit: My test app TPOPViewController.m file where the magic happens:

#import "TPOPViewController.h"

@interface TPOPViewController () <UIPopoverPresentationControllerDelegate>//, UIAdaptivePresentationControllerDelegate>

@end

@implementation TPOPViewController

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

    NSString *identifier = segue.identifier;
    if ([identifier isEqualToString:@"popover"]) {
        UIViewController *dvc = segue.destinationViewController;
        UIPopoverPresentationController *ppc = dvc.popoverPresentationController;
        if (ppc) {
            if ([sender isKindOfClass:[UIButton class]]) { // Assumes the popover is being triggered by a UIButton
                ppc.sourceView = (UIButton *)sender;
                ppc.sourceRect = [(UIButton *)sender bounds];
            }
            ppc.delegate = self;
        }
    }
}

- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {

    return UIModalPresentationNone;
}

@end

My test storyboard as well:

Popover on iPhone test storyboard

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
Robotic Cat
  • 5,899
  • 4
  • 41
  • 58
  • Well, then. Looks good. I'll be interested in @Jef's response--he seemed pretty sure. Which is why, after failing several times to make it work, I marked it as accepted. Will give this a try. Thanks much! – rattletrap99 Feb 15 '15 at 21:26
  • 1
    @rattletrap99: I've tested this. It definitely works. The whole code excluding `if` tests is basically 4 lines. I originally thought (in my comments) you had to conform to `UIAdaptivePresentationControllerDelegate` but in fact it's `UIPopoverPresentationControllerDelegate`. The trick is that the `UIPopoverPresentationController` reference has to be taken from the `segue.destinationViewController`. – Robotic Cat Feb 15 '15 at 21:30
  • I see, and very much appreciate your testing this. I'm going to give it a shot in a few minutes in a test app. Your test bears out the impression I had from my reading. – rattletrap99 Feb 15 '15 at 21:33
  • I also created a test app. I only needed a single view controller file. I'll add that to the answer. – Robotic Cat Feb 15 '15 at 21:40
  • To workaround bug on iOS 9 that arrow does not point to button, see [this](http://stackoverflow.com/questions/32666214/ios9-popover-always-points-to-top-left-corner-of-anchor) or [this](http://stackoverflow.com/questions/30064595/popover-doesnt-center-on-button/33724019#33724019). – Pang Feb 24 '16 at 08:54
  • @RoboticCat can you pls share sample project? – karthikeyan Apr 20 '16 at 09:29
  • @karthikeyan: I doubt I have the project anymore; I just created it as a throwaway example. However, the code & the storyboard picture is the *entire* project. The steps to create this are in my answer. If you're having a problem then it would be best if you create a new question. – Robotic Cat Apr 20 '16 at 11:23
  • Hey @RoboticCat...i tried it..it works perfectly...one small issue is there...May i know,how to customize the size of pop up window – Suraj Sukale Jun 02 '16 at 10:54
  • @SurajSukale: I usually `override` the `preferredContentSize:` method in the view controller being presented in the popover and return the correct `CGSize`. – Robotic Cat Jun 02 '16 at 11:21
  • @RoboticCat... can you please tell me,how can i do?...now in my popover i'm going to use tableviewcontroller...it works but i want just 3 cell..and it shows me lots of.....what should i do?how? – Suraj Sukale Jun 02 '16 at 11:29
  • Just do exactly as I wrote: `override` the `preferredContentSize:` method. Documentation: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/instp/UIViewController/preferredContentSize . If you are using a `UITableViewController` then put this method in that class. It's very simple. – Robotic Cat Jun 02 '16 at 15:03
  • I get a crash when I don't set sourceRect and sourceView... I think this should be added to the code. I'm running it on iphone7 plus sim. – C0D3 Oct 11 '16 at 17:41
  • @c0d3Junk13: Just tested the code above and it works in the simulator. Happy to add the extra lines though as the popover arrow needs extra info in iOS 10. – Robotic Cat Oct 11 '16 at 22:06
  • How would you present a view controller loaded from xib (instead of present as popover segueing )? – koira Dec 13 '16 at 19:52
  • @koira: The same way you would present a view controller without segues. You call `present(viewControllerToPresent: , animated: , completion:)`. There are lots of questions on Stack Overflow; search for `present view controller` – Robotic Cat Dec 13 '16 at 21:28
22

Apparently the above method no longer works with iOS9/Xcode7. This is because if you set the segue style to "Popover" using Interface Builder, Xcode ignores it when it compiles your application. Furthermore, it automatically sets the segue back to "Push" the next time you open your project. If you have version control software like Git, you'll be able to observe this unwanted change being made.

However, it's still possible to get iPad-style popovers on the iPhone if you manually present the view controller that you want to show as a popover. Example Swift code:

//  ViewController.swift
//  PopoverDemo
//
//  Created by bhnascar on 12/2/15.
//  Copyright © 2015 bhnascar. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {

    /* The bar button item that will present the popover. */
    var popoverButton: UIBarButtonItem?

    override func viewDidLoad() {
        super.viewDidLoad()
        popoverButton = UIBarButtonItem(title: "Pop!", style: UIBarButtonItemStyle.Plain, target: self, action: "presentPopover")
        self.navigationItem.rightBarButtonItem = popoverButton
    }

    // Mark: - UIPopoverPresentationControllerDelegate

    func prepareForPopoverPresentation(popoverPresentationController: UIPopoverPresentationController) {
        popoverPresentationController.permittedArrowDirections = .any
        popoverPresentationController.barButtonItem = popoverButton
    }

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

    // Mark: - Callback function for popover button.

    func presentPopover() {
        let popoverContentController = UIViewController()
        popoverContentController.view.backgroundColor = .blue

        // Set your popover size.
        popoverContentController.preferredContentSize = CGSize(width: 300, height: 300)

        // Set the presentation style to modal so that the above methods get called.
        popoverContentController.modalPresentationStyle = .popover

        // Set the popover presentation controller delegate so that the above methods get called.
        popoverContentController.popoverPresentationController!.delegate = self

        // Present the popover.
        self.present(popoverContentController, animated: true, completion: nil)
    }

}
beryllium
  • 29,669
  • 15
  • 106
  • 125
bhnascar
  • 289
  • 3
  • 2
  • Thanks for posting the code. Can you explain what self.navigationItem is? Do you have to setup a NavigationController/NavigationBar/and NavigationItem prior? – LevinsonTechnologies Feb 29 '16 at 01:29
  • Yes, my code assumes that you've setup a [UINavigationController](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationController_Class/) and ViewController exists somewhere in that UINavigationController's view controller hierarchy. A UINavigationController has an associated UINavigationBar and each view controller has a [UINavigationItem](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationItem_Class/) that modifies the content of the navigation bar (like titles and buttons). – bhnascar Mar 15 '16 at 23:57
  • 2
    The storyboard use of **Present As Popover** works for me in Xcode 9.4. – Clifton Labrum Jul 01 '18 at 05:35
  • does not work for me (Xcode 11, iOS 13) for regular UIButtons – Duck Jun 25 '20 at 03:01
9

SWIFT 3.X

This will show the popover at the center of the screen

class CommonViewController: UIViewController, UIPopoverPresentationControllerDelegate{

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

    func showPopover() {
        let myViewController = UIViewController()
        myViewController.preferredContentSize = CGSize(width: 320, height: 200)
        myViewController.modalPresentationStyle = .popover

        let popOver = myViewController.popoverPresentationController
        popOver?.delegate = self

        self.present(myViewController, animated: true, completion: nil)
        popOver?.permittedArrowDirections = .up
        popOver?.sourceView = self.view

        let rect = CGRect(
            origin: CGPoint(x: self.view.frame.width/2,
                            y: self.view.frame.height/2),
            size: CGSize(width: 1, height: 1)
        )
        popOver?.sourceRect = rect
    }
}
Xavier Lowmiller
  • 1,381
  • 1
  • 15
  • 24
tsik
  • 609
  • 8
  • 10
0

To present UIModalPresentationStyle popover from iPhone/iPad:

-(void)menuButtonPressed:(UIButton *)sender {

    self.menuPopoverController = [[DownloadMenuPopoverController alloc] initWithStyle:UITableViewStylePlain];
    self.menuPopoverController.delegate = self;

    self.menuPopoverController.modalPresentationStyle = UIModalPresentationPopover;
    self.menuPopoverController.popoverPresentationController.delegate = self;
    self.menuPopoverController.preferredContentSize = CGSizeMake(250,80);
    self.menuPopoverController.popoverPresentationController.sourceRect = sender.frame;// rect to show view
    self.menuPopoverController.popoverPresentationController.sourceView = self.view;

    UIPopoverPresentationController *popPC = self.menuPopoverController.popoverPresentationController;
    popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
    popPC.delegate = self;
    [self presentViewController:self.menuPopoverController animated:YES completion:nil];

}

#pragma mark - UIPresentationController Delegate methods

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

- (UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style {
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller.presentedViewController];
    return navController;
}
Harshal Wani
  • 2,249
  • 2
  • 26
  • 41
0
**Made a barbutton item like this** - 
let dotImage = UIImage(named: "dot")?.withRenderingMode(.alwaysOriginal) let searchImage = UIImage(named: "search")?.withRenderingMode(.alwaysOriginal) // let shareImageButton = UIBarButtonItem(image: shareImage, style: .plain, target: self, action: #selector(didTapshareImageButton(sender:))) let dotImageButton = UIBarButtonItem(image: dotImage, style: .plain, target: self, action: #selector(didTapdotImageButton(sender:))) let searchButton = UIBarButtonItem(image: searchImage, style: .plain, target: self, action: #selector(didTapSearchButton(sender:)))
**Added popover on this -** 
@objc func didTapdotImageButton(sender: UIBarButtonItem){
    
    let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "ShareVC")
        vc.modalPresentationStyle = .popover
        let popover: UIPopoverPresentationController = vc.popoverPresentationController!
        popover.barButtonItem = sender
        popover.delegate = self
        sender.image = UIImage(named: "close")?.withRenderingMode(.alwaysOriginal)
        present(vc, animated: true, completion:nil)
    }
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 26 '22 at 03:10