31

I'm trying to show a popup using a custom UIPopoverPresentationController class. But it crashes with the error(<UIPopoverPresentationController: 0x7a772950>) should have a non-nil sourceView or barButtonItem set before the presentation occurs. Below is my button click code where the crash occurs.

- (IBAction)showPopup:(UIButton *)sender {
ViewController *contentViewController = [[ViewController alloc] init];

    contentViewController.preferredContentSize = CGSizeMake(200, 200);
    contentViewController.modalPresentationStyle = UIModalPresentationPopover;
    myPopoverController *popOver = [[myPopoverController alloc]initWithPresentedViewController:contentViewController presentingViewController:self andTintColor:[UIColor lightGrayColor]];

    popOver.delegate = self;
    popOver.permittedArrowDirections = UIPopoverArrowDirectionUp;
    popOver.sourceRect = sender.frame;
    popOver.sourceView = self.view;
    [self presentViewController:contentViewController animated: YES completion: nil];
}

Below is a sample of how my custom UIPopoverPresentationController looks like

myPopoverController.h file

@interface myPopoverController : UIPopoverPresentationController

@property (readonly) UIColor *tintColor;


-(instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController andTintColor:(UIColor *)aTintColor;

@end


myPopoverController.m file

//Some code for UIPopoverBackgroundView

-(instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController
{

    self = [self initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController andTintColor: [UIColor redColor]];

    return self;
}


-(instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController andTintColor:(UIColor *)aTintColor
{

    self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];

    if (!self) {
        return nil;
    }

    [super setPopoverBackgroundViewClass: [myPopoverControllerBackgroundView class]];
    tintColor = aTintColor;


    return self;
}

I don't have a barbutton but I'm setting the sourceView. Am I doing something wrong here? Appreciate your help

Francis F
  • 3,157
  • 3
  • 41
  • 79

6 Answers6

12

You can create a popover presentation controller like this also and it may work

- (IBAction)showPopup:(UIButton *)sender {

ViewController *contentViewController = [[ViewController alloc] init];
    contentViewController.preferredContentSize = CGSizeMake(200, 200);
    contentViewController.modalPresentationStyle = UIModalPresentationPopover;

UIPopoverPresentationController *popoverpresentationController = contentViewController.popoverPresentationController;
    popoverpresentationController.delegate = self;
    popoverpresentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;
    popoverpresentationController.sourceRect = sender.bounds;
    popoverpresentationController.sourceView = sender;
    [self presentViewController:contentViewController animated: YES completion: nil];
}
Martin
  • 846
  • 1
  • 9
  • 23
10

If sourceView is null, just add a validation

UIActivityViewController * avc = [[UIActivityViewController alloc] initWithActivityItems:shareItems applicationActivities:nil];
if(avc.popoverPresentationController){
    avc.popoverPresentationController.sourceView = self.view;
}
[self presentViewController:avc animated:YES completion:nil];

See this post

Erick Dávila
  • 347
  • 5
  • 9
  • 3
    This worked for me. I was encountering a crash only on iPad (and not iPhone or iPod Touch). Actually I had to replace `.sourceView` with `.barButtonItem` and set it equal to the bar button item I had added to my `navigationController.navigationItem`. – Chris Allinson Mar 06 '21 at 00:56
  • You also have to set the `sourceRect` otherwise the popover won't display – kbunarjo Aug 11 '21 at 18:59
  • You need to set `popOver.permittedArrowDirections = 0;`, so that there are no arrows. If not set the popup didn't display for me (maybe because my source view is full screen). If I had arrows I needed to specify `.sourceRect` as well. I gave it the view.bounds but maybe because it was full screen view it didn't show the popup. I gave it something "imaginary" `CGRectMake(view.origin.x, view.origin.y, 0, 0);` – h3dkandi Jun 17 '22 at 13:20
  • This answer is incomplete. I tried this code and I did not see any popover. – Rohit Singh Sep 23 '22 at 02:46
6

You are subclassing the UIPopoverPresentationController but Apple recommends to use them as they are. Once you present a UIViewController, a UIPopoverPresentationController will be automatically created and you are supposed to modify it for your needs.

You create an myPopoverController instance but Apple creates another when you present your contentViewController right after:

[self presentViewController:contentViewController animated: YES completion: nil];

This new UIPopoverPresentationController lacks the sourceView and throws an exception.

Try the code below instead:

ViewController *contentViewController = [[ViewController alloc] init];

// Present the view controller using the popover style.
contentViewController.modalPresentationStyle = UIModalPresentationPopover;
[self presentViewController:contentViewController 
                   animated:YES 
                 completion:nil];

// Get the popover presentation controller and configure it.
UIPopoverPresentationController *presentationController =[contentViewController popoverPresentationController];
presentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;
presentationController.sourceView = self.view;
presentationController.sourceRect = sender.frame;
Hemang
  • 26,840
  • 19
  • 119
  • 186
Yunus Nedim Mehel
  • 12,089
  • 4
  • 50
  • 56
2

May be below code could help:

In iPad the view controller will be displayed as a popover using the new UIPopoverPresentationController, it requires to specify an anchor point for the presentation of the popover using one of the three following properties:

  1. barButtonItem
  2. sourceView
  3. sourceRect

Do as follows:

//for iPhone
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {

    [self presentViewController:controller animated:YES completion:nil];

}
//for iPad
else {
    // Change Rect as required
    ViewController *contentViewController = [[ViewController alloc] init];

    contentViewController.preferredContentSize = CGSizeMake(200, 200);
    contentViewController.modalPresentationStyle = UIModalPresentationPopover;
    [self presentViewController:contentViewController animated:YES completion:nil];
}
Ronak Chaniyara
  • 5,335
  • 3
  • 24
  • 51
2

The Best way I think is override present method

extension XXXBaseViewController: UIPopoverPresentationControllerDelegate {

    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        if let popController = viewControllerToPresent.popoverPresentationController,
            popController.sourceView == nil{
            return
        }
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
}
kkklc
  • 149
  • 7
  • I once encountered a situation。If there is not enough memory, it will be automatically reclaimed – kkklc Jun 04 '20 at 09:39
0

my two cents for activity (and in swift 5..)

..

 let activityViewController = UIActivityViewController(
                activityItems: shareTextAndImg, applicationActivities: [])
 if UIDevice.current.userInterfaceIdiom == .pad {
   activityViewController.popoverPresentationController?.barButtonItem = btn
  }

present(activityViewController, animated: true)
ingconti
  • 10,876
  • 3
  • 61
  • 48