user3642915
user3642915

Reputation: 203

Popover doesn't center on button

I am trying to center a popover on a button. I can't seem to figure out where I might be going wrong. Instead of the arrow being in the middle of the button, it is off center by half the width of the screen.

 @IBAction func buttonClicked(sender: AnyObject){
    var popoverViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ServiceOptions") as! ServiceOptionsPopover
    popoverViewController.delegate = self
    popoverViewController.modalPresentationStyle = .Popover
    popoverViewController.preferredContentSize   = CGSizeMake(300, 300)

    let popoverPresentationViewController = popoverViewController.popoverPresentationController

    popoverPresentationViewController?.permittedArrowDirections = .Up
    popoverPresentationViewController?.delegate = self
    popoverPresentationViewController?.sourceView = sender as! UIButton
    popoverPresentationViewController?.sourceRect = sender.frame

    presentViewController(popoverViewController, animated: true, completion: nil)
}

Upvotes: 20

Views: 12361

Answers (4)

93sauu
93sauu

Reputation: 4097

In my case the problem is different, the popover is shown for a UIBarButtonItem with a custom view. For iOS 11 if you use custom view of UIBarButtonItem, the custom view needs to be auto layout friendly.

With this category you can apply quickly the constraints.

UIView+NavigationBar.h

@interface UIView (NavigationBar)

    - (void)applyNavigationBarConstraints:(CGFloat)width height:(CGFloat)height;
    - (void)applyNavigationBarConstraintsWithCurrentSize;

@end

UIView+NavigationBar.m

#import "UIView+NavigationBar.h"

@implementation UIView (NavigationBar)

- (void)applyNavigationBarConstraints:(CGFloat)width height:(CGFloat)height
{
    if (width == 0 || height == 0) {
        return;
    }

    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:height];
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:width];
    [heightConstraint setActive:TRUE];
    [widthConstraint setActive:TRUE];
}

- (void)applyNavigationBarConstraintsWithCurrentSize {
    [self applyNavigationBarConstraints:self.bounds.size.width height:self.bounds.size.height];
}

@end

Then you can do:

UIButton *buttonMenu = [UIButton buttonWithType:UIButtonTypeCustom];
[buttonMenu setImage:[UIImage imageNamed:@"menu"] forState:UIControlStateNormal];
buttonMenu.frame = CGRectMake(0, 0, 44, 44);
[buttonMenu addTarget:self action:@selector(showMenu:) forControlEvents:UIControlEventTouchUpInside];

//Apply constraints 
[buttonMenu applyNavigationBarConstraintsWithCurrentSize];

UIBarButtonItem *menuBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:buttonMenu];

One time you apply the constraints the popover is shown correctly over custom view, e.g., the code for showing a alert as popover is:

UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Menu" message:@"" preferredStyle:UIAlertControllerStyleActionSheet];
//Add actions ....

UIPopoverPresentationController *popController = [controller popoverPresentationController];
popController.sourceView = buttonMenu;
popController.sourceRect = buttonMenu.bounds;

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

Upvotes: 2

Scott Gardner
Scott Gardner

Reputation: 8739

There is an issue in iOS 9. Setting the anchor in a storyboard:

enter image description here

...results in the arrow not being centered on the anchor:

enter image description here

To resolve, add this to prepareForSegue:sender::

// Fixes popover anchor centering issue in iOS 9
if let popoverPresentationController = segue.destinationViewController.popoverPresentationController, sourceView = sender as? UIView {
  popoverPresentationController.sourceRect = sourceView.bounds
}

enter image description here

Upvotes: 49

matt
matt

Reputation: 534925

The problem is the elementary one of confusing frame and bounds:

popoverPresentationViewController?.sourceView = sender as! UIButton
popoverPresentationViewController?.sourceRect = sender.frame

No! You mean bounds:

popoverPresentationViewController?.sourceView = sender as! UIButton
popoverPresentationViewController?.sourceRect = (sender as! UIButton).bounds

The reason is that the sourceRect is given in the coordinate space of the sourceView - that is, if you want it to be the view's rect, it's the bounds of that view.

Upvotes: 60

Vijay Masiwal
Vijay Masiwal

Reputation: 1153

Here is the right way:

@IBAction func buttonClicked(sender: UIButton){
    var popoverViewController = UIViewController()
    popoverViewController.view.frame = CGRectMake(0,0, 300, 300)
    popoverViewController.view.backgroundColor = UIColor.redColor()
    popoverViewController.modalPresentationStyle = .Popover
    popoverViewController.preferredContentSize   = CGSizeMake(300, 300)

    let popoverPresentationViewController = popoverViewController.popoverPresentationController

    popoverPresentationViewController?.permittedArrowDirections = .Up
    popoverPresentationViewController?.sourceView = sender
    popoverPresentationViewController?.sourceRect = CGRectMake(0, 0, sender.bounds.width,sender.bounds.height) // see this line of code

    presentViewController(popoverViewController, animated: true, completion: nil)
}

Upvotes: 3

Related Questions