How to present popover properly in iOS 8
Asked Answered
E

13

120

I'm trying to add a UIPopoverView to my Swift iOS 8 app, but I am unable to access the PopoverContentSize property, as the popover does not show in the correct shape. my code:

var popover: UIPopoverController? = nil 

    func addCategory() {

    var newCategory = storyboard.instantiateViewControllerWithIdentifier("NewCategory") as UIViewController
    var nav = UINavigationController(rootViewController: newCategory)
    popover = UIPopoverController(contentViewController: nav)
    popover!.setPopoverContentSize(CGSizeMake(550, 600), animated: true)
    popover!.delegate = self
    popover!.presentPopoverFromBarButtonItem(self.navigationItem.rightBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}

output:

enter image description here

When I am doing the same thing through UIPopoverPresentationController, I still don't get it done. this is my code:

func addCategory() {

    var popoverContent = self.storyboard.instantiateViewControllerWithIdentifier("NewCategory") as UIViewController
    var nav = UINavigationController(rootViewController: popoverContent)
    nav.modalPresentationStyle = UIModalPresentationStyle.Popover
    var popover = nav.popoverPresentationController as UIPopoverPresentationController
    popover.delegate = self
    popover.popoverContentSize = CGSizeMake(1000, 300)
    popover.sourceView = self.view
    popover.sourceRect = CGRectMake(100,100,0,0)

    self.presentViewController(nav, animated: true, completion: nil)

}

I get the exact same output.

How do I customize the size of my popover? Any help would be highly appreciated!

Ep answered 8/7, 2014 at 15:24 Comment(2)
There is a WWDC video on the developer site called "A Look Inside Presentation Controllers" It explains how to use the UIPopoverPresentationControllerMylander
I have edited my question according to the apple video regarding the UIpopoverpresentationctontroller, but nothing changed! do you maybe se anything that I should change about this? Thanks for the input though!Ep
E
150

Okay, A housemate took a look at it and figured it out:

 func addCategory() {

    var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("NewCategory") as UIViewController
    var nav = UINavigationController(rootViewController: popoverContent)
    nav.modalPresentationStyle = UIModalPresentationStyle.Popover
    var popover = nav.popoverPresentationController
    popoverContent.preferredContentSize = CGSizeMake(500,600)
    popover.delegate = self
    popover.sourceView = self.view
    popover.sourceRect = CGRectMake(100,100,0,0)

    self.presentViewController(nav, animated: true, completion: nil)

}

That's the way.

You don't talk to the popover itself anymore, you talk to the view controller inside of it to set the content size, by calling the property preferredContentSize

Ep answered 10/7, 2014 at 22:12 Comment(12)
Probably stating the obvious, but this is not just swift-related. I also had to do this in my obj-c app :)Garrote
Another comment on the code - you can use "let" instead of "var". Apple recommends it for cases where you don't need to reassign the value.Karlow
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.Chainplate
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.Chainplate
@PsychoDad can u please provide link to this solution u mentioned. I am still stuck at "while the simulator is in portrait, it is always full screen". ThanksLoats
Is it working properly in iOS 8.1 as well ? In my case it's working fine in iOS 8.0 but not in iOS 8.1. Can Anyone help it out ?Bilge
preferredContentSize is readonly in iOS8.1. Also UIPopoverPresentationController.popoverContentSize is not there anymore either. How are people handling size change?Pickering
I'm pretty sure that whenever you have width regular (e.g. iphone in portrait mode) these popovers are forced to be full-screen and there is no way to avoid it.Skilled
Why is sourceRect looks invalid?Ziska
Can you give objective c version of itMaurreen
this API just from IOS 8,but I need support IOS 7Alimentation
How to do it without storyboard?Caputo
G
53

Actually it is much simpler than that. In the storyboard you should make the viewcontroller you want to use as popover and make a viewcontroller class for it as usual. Make a segue as shown below from the object you want to open the popover, in this case the UIBarButton named "Config".

enter image description here

In the "mother viewcontroller" implement the UIPopoverPresentationControllerDelegate and the delegate method:

func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
    //do som stuff from the popover
}

Override the prepareForSeque method like this:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     //segue for the popover configuration window
    if segue.identifier == "yourSegueIdentifierForPopOver" {
        if let controller = segue.destinationViewController as? UIViewController {
            controller.popoverPresentationController!.delegate = self
            controller.preferredContentSize = CGSize(width: 320, height: 186)
        }
    }
}

And you're done. And you can now treat the popover view as any other view, ie. add fields and what not! And you get hold of the the content controller by using the popoverPresentationController.presentedViewController method in the UIPopoverPresentationController.

Also on an iPhone you would have to overwrite

func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {

        return UIModalPresentationStyle.none
    } 
Gensmer answered 26/1, 2015 at 20:27 Comment(0)
W
28

I found a complete example of how to get this all to work so that you can always display a popover no matter the device/orientation https://github.com/frogcjn/AdaptivePopover_iOS8_Swift.

The key is to implement UIAdaptivePresentationControllerDelegate

func adaptivePresentationStyleForPresentationController(PC: UIPresentationController!) -> UIModalPresentationStyle {
    // This *forces* a popover to be displayed on the iPhone
    return .None
}

Then extend the example above (from Imagine Digital):

nav.popoverPresentationController!.delegate = implOfUIAPCDelegate
Wyattwyche answered 24/9, 2014 at 20:0 Comment(4)
I use UIPopoverPresentationControllerDelegateSpecht
Correct, UIPopoverPresentationControllerDelegate extends UIAdaptivePresentationControllerDelegate. So, by definition, both contain the method 'adaptivePresentationStyleForPresentationController'. I provided the base interface as that is where the method is documented in Apple's API documents.Wyattwyche
Note this is undocumented behavior. The doc says this delegate method must return "either UIModalPresentationFullScreen or UIModalPresentationOverFullScreen". Furthermore, "If you do not implement this method or return any style other than UIModalPresentationFullScreen or UIModalPresentationOverFullScreen, the presentation controller adjusts the presentation style to the UIModalPresentationFullScreen style."Witha
Current documentation advises that from iOS 8.3 onwards you should use - adaptivePresentationStyleForPresentationController:traitCollection: and that returned style must be "UIModalPresentationFullScreen, UIModalPresentationOverFullScreen, UIModalPresentationFormSheet, or UIModalPresentationNone."Stuck
F
25

Swift 2.0

Well I worked out. Have a look. Made a ViewController in StoryBoard. Associated with PopOverViewController class.

import UIKit

class PopOverViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()    
        self.preferredContentSize = CGSizeMake(200, 200)    
        self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "dismiss:")    
    }    
    func dismiss(sender: AnyObject) {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}      

See ViewController:

//  ViewController.swift

import UIKit

class ViewController: UIViewController, UIPopoverPresentationControllerDelegate
{
    func showPopover(base: UIView)
    {
        if let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("popover") as? PopOverViewController {    

            let navController = UINavigationController(rootViewController: viewController)
            navController.modalPresentationStyle = .Popover

            if let pctrl = navController.popoverPresentationController {
                pctrl.delegate = self

                pctrl.sourceView = base
                pctrl.sourceRect = base.bounds

                self.presentViewController(navController, animated: true, completion: nil)
            }
        }
    }    
    override func viewDidLoad(){
        super.viewDidLoad()
    }    
    @IBAction func onShow(sender: UIButton)
    {
        self.showPopover(sender)
    }    
    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
        return .None
    }
}  

Note: The func showPopover(base: UIView) method should be placed before ViewDidLoad. Hope it helps !

Fiester answered 11/9, 2015 at 10:47 Comment(2)
hi @Alvin, I am going to popup a view from map annotation. so i did the same as you did . the difference is that am going to populate tableviewcontroller instead of view. Now the problem is not hitting delegate method ."popoverPresentationControllerDidDismissPopover". when i dismiss the controller. can you help ? (question is not related to the post)Engineman
Why a showPopover(base: UIView) method should be placed before viewDidLoad()?Floss
B
15

In iOS9 UIPopoverController is depreciated. So can use the below code for Objective-C version above iOS9.x,

- (IBAction)onclickPopover:(id)sender {
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
UIViewController *viewController = [sb instantiateViewControllerWithIdentifier:@"popover"];

viewController.modalPresentationStyle = UIModalPresentationPopover;
viewController.popoverPresentationController.sourceView = self.popOverBtn;
viewController.popoverPresentationController.sourceRect = self.popOverBtn.bounds;
viewController.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
[self presentViewController:viewController animated:YES completion:nil]; }
Bathos answered 5/3, 2016 at 15:28 Comment(1)
The question specifically asks for Swift, not for Objective-C.Court
B
9

Here i Convert "Joris416" Swift Code to Objective-c,

-(void) popoverstart
{
    ViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"PopoverView"];
    UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:controller];
    nav.modalPresentationStyle = UIModalPresentationPopover;
    UIPopoverPresentationController *popover = nav.popoverPresentationController;
    controller.preferredContentSize = CGSizeMake(300, 200);
    popover.delegate = self;
    popover.sourceView = self.view;
    popover.sourceRect = CGRectMake(100, 100, 0, 0);
    popover.permittedArrowDirections = UIPopoverArrowDirectionAny;
    [self presentViewController:nav animated:YES completion:nil];
}

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

Remember to ADD
UIPopoverPresentationControllerDelegate, UIAdaptivePresentationControllerDelegate

Burton answered 24/8, 2015 at 6:44 Comment(1)
The question specifically asks for Swift, not for Objective-C.Court
K
4

This is best explained on the iOS8 Day-by-Day blog

In short, once you've set your UIViewController's modalPresentationStyle to .Popover, you can get hold of a UIPopoverPresentationClass (a new iOS8 class) via the controller's popoverPresentationController property.

Kianakiang answered 14/10, 2014 at 11:1 Comment(0)
M
3

I made an Objective-C version of Imagine Digitals swift answer above. I don't think I missed anything as it seems to work under preliminary testing, if you spot something let me know, and I'll update it

-(void) presentPopover
{
    YourViewController* popoverContent = [[YourViewController alloc] init]; //this will be a subclass of UIViewController
    UINavigationController* nav =  [[UINavigationController alloc] initWithRootViewController:popoverContent];
    nav.modalPresentationStyle = UIModalPresentationPopover;
    UIPopoverPresentationController* popover = nav.popoverPresentationController;
    popoverContent.preferredContentSize = CGSizeMake(500,600);
    popover.delegate = self;
    popover.sourceRect = CGRectMake(100,100,0,0); //I actually used popover.barButtonItem = self.myBarButton;

    [self presentViewController:nav animated:YES completion:nil];
}
Morril answered 5/5, 2015 at 18:7 Comment(3)
I think you left out popover.sourceView = self.view;Pearly
The question specifically asks for Swift, not for Objective-C.Court
I realise that, but google brings you here even if you are searching for objective-C. That's how I ended up here.Morril
J
3

my two cents for xcode 9.1 / swift 4.

class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {

    override func viewDidLoad(){
        super.viewDidLoad()

        let when = DispatchTime.now() + 0.5

        DispatchQueue.main.asyncAfter(deadline: when, execute: { () -> Void in
            // to test after 05.secs... :)
            self.showPopover(base: self.view)

        })

}


func showPopover(base: UIView) {
    if let viewController = self.storyboard?.instantiateViewController(withIdentifier: "popover") as? PopOverViewController {

        let navController = UINavigationController(rootViewController: viewController)
        navController.modalPresentationStyle = .popover

        if let pctrl = navController.popoverPresentationController {
            pctrl.delegate = self

            pctrl.sourceView = base
            pctrl.sourceRect = base.bounds

            self.present(navController, animated: true, completion: nil)
        }
    }
}


@IBAction func onShow(sender: UIButton){
    self.showPopover(base: sender)
}

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

and experiment in:

func adaptivePresentationStyle...

    return .popover

or: return .pageSheet.... and so on..

Jones answered 14/12, 2017 at 20:33 Comment(0)
T
2

Implement UIAdaptivePresentationControllerDelegate in your Viewcontroller. Then add :

func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle{
    return .none
}
Tiffie answered 30/11, 2016 at 10:41 Comment(0)
H
1

The following has a pretty comprehensive guide on how to configure and present popovers. https://www.appcoda.com/presentation-controllers-tutorial/

In summary, a viable implementation (with some updates from the original article syntax for Swift 4.2), to then be called from elsewhere, would be something like the following:

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.permittedArrowDirections = UIPopoverArrowDirection.any
    }
    self.present(popoverViewController, animated: true)
}

A lot of this was already covered in the answer from @mmc, but the article helps to explain some of those code elements used, and also show how it could be expanded.

It also provides a lot of additional detail about using delegation to handle the presentation style for iPhone vs. iPad, and to allow dismissal of the popover if it's ever shown full-screen. Again, updated for Swift 4.2:

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

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:
        let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
        let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target: self, action: #selector(doneWithPopover))
        navigationController.topViewController?.navigationItem.rightBarButtonItem = doneButton
        return navigationController
    default:
        return controller.presentedViewController
    }
}

// As of Swift 4, functions used in selectors must be declared as @objc
@objc private func doneWithPopover() {
    self.dismiss(animated: true, completion: nil)
}

Hope this helps.

Herbartian answered 14/3, 2019 at 17:3 Comment(0)
T
1
 var popoverContent = (self.storyboard?.instantiateViewController(withIdentifier: "WhatsNewViewController"))! as UIViewController
        var nav = UINavigationController(rootViewController: popoverContent)
    nav.modalPresentationStyle = UIModalPresentationStyle.popover
        var popover = nav.popoverPresentationController
        popoverContent.preferredContentSize = CGSize(width: 500, height: 100)
        popover?.delegate = self
        popover?.sourceView = self.view
        popover?.sourceRect = CGRectMake(100,100,0,0)

    self.present(nav, animated: true, completion: nil)
Tenter answered 15/3, 2021 at 12:22 Comment(1)
in the latest Xcode we can write like thatTenter
F
0

For those who wants to study!

I created an Open Source project for those who want to study and use Popover view for anypurpose. You can find the project here. https://github.com/tryWabbit/KTListPopup

KTListNewResize

Fairing answered 11/7, 2019 at 7:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.