Popover doesn't center on button

2020-02-24 12:53发布

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)
}

4条回答
Viruses.
2楼-- · 2020-02-24 13:29

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

查看更多
Summer. ? 凉城
3楼-- · 2020-02-24 13:36

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];
查看更多
Deceive 欺骗
4楼-- · 2020-02-24 13:45

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)
}
查看更多
Deceive 欺骗
5楼-- · 2020-02-24 13:53

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.

查看更多
登录 后发表回答