21

旋转后,我无法在屏幕上保持弹出窗口的相同位置。有什么好的方法可以做到这一点,因为在旋转后只是将一些框架设置为弹出框会很糟糕。popover.frame = CGRectMake(someFrame);旋转后弹出框只有在屏幕中央时才看起来不错。

4

13 回答 13

30

苹果有一个关于这个问题的问答。您可以在此处找到详细信息:

技术问答 QA1694 在方向更改期间处理弹出框控制器

基本上,该技术解释了在您的视图控制器的didRotateFromInterfaceOrientation方法中,您将再次呈现弹出窗口,如下所示:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [aPopover presentPopoverFromRect:targetRect.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}

有关更多信息,请阅读上面的文章以及UIPopoverController 类参考

如果用户在弹出框可见时旋转设备,弹出框控制器会隐藏弹出框,然后在旋转结束时再次显示。弹出框控制器尝试为您适当地定位弹出框,但在某些情况下您可能必须再次显示或完全隐藏它。例如,当从条形按钮项显示时,弹出框控制器会自动调整弹出框的位置(可能还有大小)以考虑条形按钮项位置的变化。但是,如果您在旋转期间移除了条形按钮项目,或者如果您从视图中的目标矩形呈现弹出框,则弹出框控制器不会尝试重新定位弹出框。在这些情况下,您必须手动隐藏弹出框或从适当的新位置再次显示它。

于 2013-04-17T15:50:29.270 回答
17

从 iOS 8.0.2 开始, willRotateToInterfaceOrientation不会有任何效果。正如 mhrrt 提到的,您需要使用委托方法:

- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view

因此,例如,如果您希望弹出框直接显示在按下的按钮下方,您将使用以下代码:

- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
{
   CGRect rectInView = [self.theButton convertRect:self.theButton.frame toView:self.view];
   *rect = CGRectMake(CGRectGetMidX(rectInView), CGRectGetMaxY(rectInView), 1, 1);
   *view = self.view;
}
于 2014-10-09T13:56:17.483 回答
6

在 iOS 7 中,您可以使用- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view重新定位 UIPopoverController 的界面方向更改视图。

请参阅UIPopoverControllerDelegate 文档

于 2014-02-03T12:41:26.637 回答
5

您可以在didRotateFromInterfaceOrientation:用于呈现弹出框的视图控制器的方法中执行此操作。

使用setPopoverContentSize:animated:方法设置弹出框的大小。

于 2012-01-30T14:21:13.027 回答
4

UIPopoverController在 ios9 中被弃用,取而代之的是在 ios8 中引入的 UIPopoverPresentationController。UIActionSheet(从到时,我也经历了这种转变UIAlertController。)您有两种选择(obj-C 中的示例):

A. 实现UIViewController下面的方法(UIKit 在改变呈现的视图控制器的视图大小之前调用这个方法)。

- (void)viewWillTransitionToSize:(CGSize)size
           withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
        [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
        [coordinator animateAlongsideTransition:nil
                                     completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
                                         // Fix up popover placement if necessary, *after* the transition.
                                         // Be careful here if a subclass also overrides this method.
                                         if (self.presentedViewController) {
                                             UIPopoverPresentationController *presentationController =
                                                     [self.presentedViewController popoverPresentationController];
                                             UIView *selectedView = /** YOUR VIEW */;
                                             presentationController.sourceView = selectedView.superview;
                                             presentationController.sourceRect = selectedView.frame;
                                         }
                                     }];
    }

B. 或者,在配置你UIPopoverPresentationController的呈现时,也设置它的代表。例如,您的演示 vc 可以实现UIPopoverPresentationControllerDelegate并将自己指定为代表。然后实现委托方法:

- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController
          willRepositionPopoverToRect:(inout CGRect *)rect
                               inView:(inout UIView * _Nonnull *)view {
    UIView *selectedView = /** YOUR VIEW */;
    // Update where the arrow pops out of in the view you selected.
    *view = selectedView;
    *rect = selectedView.bounds;
}
于 2017-01-10T04:13:13.317 回答
4

对于斯威夫特:

func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>)
{
    rect.pointee = CGRect(x: self.view.frame.size.width, y: 0, width: 1, height: 1) // Set new rect here
}
于 2017-07-25T11:19:41.690 回答
2

我试图设置新的矩形(rect.initialize(...))并且它有效。

func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer<CGRect>, inView view: AutoreleasingUnsafeMutablePointer<UIView?>) {

        if popoverPresentationController.presentedViewController.view.tag == Globals.PopoverTempTag
        {
            rect.initialize(getForPopupSourceRect())
        }
    }
于 2016-02-09T10:45:07.923 回答
1

我有类似的问题,我通过这个解决了

[myPop presentPopoverFromRect:myfield.frame inView:myscrollview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

myfield您要从中显示弹出框的框架在哪里,并且myscrollview是容器视图,您可以在其中将弹出框添加为子视图(在我的情况下,它是我的滚动视图,而不是放置inView:self.viewI use inView:myscrollview)。

于 2012-05-19T06:16:03.623 回答
1
  1. 初始化 PopOver 控制器

    var popoverContent: PopoverContentViewController?
    
  2. 为 PopOver 控制器编写定义

    popoverContent = self.storyboard?.instantiateViewController(withIdentifier: "PopoverContentViewController") as? PopoverContentViewController
    popoverContent?.modalPresentationStyle = .popover
    let popover = popoverContent?.popoverPresentationController!
    popover?.delegate = self
    popoverContent?.preQuestionInfoPopUpViewDelegateObject = self
    popover?.permittedArrowDirections = UIPopoverArrowDirection()
    popover?.sourceView = self.view
    popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 330, height: 330)
    
  3. 呈现 PopOver 控制器

    self.present(popoverContent,动画:true,完成:nil)

  4. 编写以下方法并将新大小分配给弹出框:

    覆盖 func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { let popover = popoverContent?.popoverPresentationController! popover?.sourceRect = CGRect(x: size.width/2, y: size.height/2, width: 0, height: 0) }

于 2019-05-28T10:38:51.683 回答
0

我有同样的问题。我没有通过跟踪呈现它的源矩形/视图来执行-presentPopoverFromRect每次操作,而是将UIPopoverController. 完成后,您所要做的就是设置必须显示弹出框的 UIBarButtonItem / UIView 。您甚至可以选择显示自定义框架中的弹出框,该框可以作为 NSString 值传入。

CSPopoverController.h

#import <UIKit/UIKit.h>

// The original popover controller would not re-orientate itself when the orientation change occurs. To tackle that issue, this subclass is created
@interface CSPopoverController : UIPopoverController

@property (nonatomic, strong) NSString *popoverDisplaySourceFrame;  // Mutually Exclusive. If you want to set custom rect as source, make sure that popOverDisplaySource is nil
@property (nonatomic, strong) id popoverDisplaySource;              // Mutually exclusive. If UIBarButtonItem is set to it, popoverDisplaySourceFrame is neglected.
@property (nonatomic, strong) UIView *popoverDisplayView;

@property (nonatomic, assign, getter = shouldAutomaticallyReorientate) BOOL automaticallyReorientate;

-(void)reorientatePopover;

@end

CSPopoverController.m

#import "CSPopoverController.h"

@implementation CSPopoverController
@synthesize popoverDisplaySourceFrame = popoverDisplaySourceFrame_;
-(NSString*)popoverDisplaySourceFrame
{
    if (nil==popoverDisplaySourceFrame_)
    {
        if (nil!=self.popoverDisplaySource)
        {
            if ([self.popoverDisplaySource isKindOfClass:[UIView class]])
            {
                UIView *viewSource = (UIView*)self.popoverDisplaySource;
                [self setPopoverDisplaySourceFrame:NSStringFromCGRect(viewSource.frame)];
            }
        }
    }
    return popoverDisplaySourceFrame_;
}
-(void)setPopoverDisplaySourceFrame:(NSString *)inPopoverDisplaySourceFrame
{
    if (inPopoverDisplaySourceFrame!=popoverDisplaySourceFrame_)
    {
        popoverDisplaySourceFrame_ = inPopoverDisplaySourceFrame;
        [self reorientatePopover];
    }
}
@synthesize popoverDisplaySource = popoverDisplaySource_;
-(void)setPopoverDisplaySource:(id)inPopoverDisplaySource
{
    if (inPopoverDisplaySource!=popoverDisplaySource_)
    {
        [self unlistenForFrameChangeInView:popoverDisplaySource_];
        popoverDisplaySource_ = inPopoverDisplaySource;
        [self reorientatePopover];

        if ([popoverDisplaySource_ isKindOfClass:[UIView class]])
        {
            UIView *viewSource = (UIView*)popoverDisplaySource_;
            [self setPopoverDisplaySourceFrame:NSStringFromCGRect(viewSource.frame)];
        }
        if (self.shouldAutomaticallyReorientate)
        {
            [self listenForFrameChangeInView:popoverDisplaySource_];
        }
    }
}
@synthesize popoverDisplayView = popoverDisplayView_;
-(void)setPopoverDisplayView:(UIView *)inPopoverDisplayView
{
    if (inPopoverDisplayView!=popoverDisplayView_)
    {
        popoverDisplayView_ = inPopoverDisplayView;
        [self reorientatePopover];
    }
}
@synthesize automaticallyReorientate = automaticallyReorientate_;
-(void)setAutomaticallyReorientate:(BOOL)inAutomaticallyReorientate
{
    if (inAutomaticallyReorientate!=automaticallyReorientate_)
    {
        automaticallyReorientate_ = inAutomaticallyReorientate;
        if (automaticallyReorientate_)
        {
            [self listenForAutorotation];
            [self listenForFrameChangeInView:self.popoverDisplaySource];
        }
        else
        {
            [self unlistenForAutorotation];
            [self unlistenForFrameChangeInView:self.popoverDisplaySource];
        }
    }
}

-(void)listenForAutorotation
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(orientationChanged:)
                                                 name:UIDeviceOrientationDidChangeNotification
                                               object:nil];
}

-(void)unlistenForAutorotation
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIDeviceOrientationDidChangeNotification
                                                  object:nil];
}

-(void)listenForFrameChangeInView:(id)inView
{
    // Let's listen for changes in the view's frame and adjust the popover even if the frame is updated
    if ([inView isKindOfClass:[UIView class]])
    {
        UIView *viewToObserve = (UIView*)inView;
        [viewToObserve addObserver:self
                        forKeyPath:@"frame"
                           options:NSKeyValueObservingOptionNew
                           context:nil];
    }
}

-(void)unlistenForFrameChangeInView:(id)inView
{
    if ([inView isKindOfClass:[UIView class]])
    {
        UIView *viewToObserve = (UIView*)inView;
        [viewToObserve removeObserver:self
                           forKeyPath:@"frame"];
    }
}

// TODO: Dealloc is not called, check why? !!!
- (void)dealloc
{
    [self unlistenForFrameChangeInView:self.popoverDisplaySource];
    [self unlistenForAutorotation];
    DEBUGLog(@"dealloc called for CSPopoverController %@", self);
}

#pragma mark - Designated initializers
-(id)initWithContentViewController:(UIViewController *)viewController
{
    self = [super initWithContentViewController:viewController];
    if (self)
    {
        [self popoverCommonInitializations];
    }
    return self;
}

-(void)popoverCommonInitializations
{
    [self setAutomaticallyReorientate:YES];
}

#pragma mark - Frame
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object==self.popoverDisplaySource)
    {
        [self setPopoverDisplaySourceFrame:nil];
        [self reorientatePopover];
    }
}

#pragma mark - Orientation
-(void)orientationChanged:(NSNotification *)inNotification
{
    [self reorientatePopover];
}

-(void)reorientatePopover
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self
                                             selector:@selector(performReorientatePopover)
                                               object:nil];
//    if ([self isPopoverVisible])
    {
        [self performSelector:@selector(performReorientatePopover)
                   withObject:nil
                   afterDelay:0.0];
    }
}

-(void)performReorientatePopover
{
    if (self.popoverDisplaySourceFrame && self.popoverDisplayView)
    {
        [self presentPopoverFromRect:CGRectFromString(self.popoverDisplaySourceFrame)
                              inView:self.popoverDisplayView
            permittedArrowDirections:UIPopoverArrowDirectionAny
                            animated:YES];
    }
    else if (self.popoverDisplaySource && [self.popoverDisplaySource isKindOfClass:[UIBarButtonItem class]])
    {
        UIBarButtonItem *barButton = (UIBarButtonItem*)self.popoverDisplaySource;
        [self presentPopoverFromBarButtonItem:barButton
                     permittedArrowDirections:UIPopoverArrowDirectionAny
                                     animated:YES];
    }
}

@end

用法:

如果它是您展示它的 UIBarButtonItem :

CSPopoverController *popOverCont = [[CSPopoverController alloc]initWithContentViewController:navCont];
self.popOver = popOverCont;
[popOverCont setPopoverDisplaySource:self.settingsButtonItem];

如果它是您呈现弹出框的 UIView:

CSPopoverController *popOver = [[CSPopoverController alloc] initWithContentViewController:navigation];
self.iPadPopoverController = popOver;
[newDateVC setIPadPopoverController:self.iPadPopoverController];
[popOver setPopoverDisplaySource:inButton];
[popOver setPopoverDisplayView:inView];
于 2013-10-21T12:29:41.083 回答
0

对于 iOS > 8 John Strickers 的回答有所帮助,但没有做我想做的事。

这是对我有用的解决方案。(如果您想下载完整的示例项目,请访问:https ://github.com/appteur/uipopoverExample )

我创建了一个属性来保存我想要呈现的任何弹出框,还添加了一个属性来跟踪 sourceRect 和另一个用于查看我希望弹出框箭头指向的按钮的视图。

@property (nonatomic, weak) UIView *activePopoverBtn;
@property (nonatomic, strong) PopoverViewController *popoverVC;
@property (nonatomic, assign) CGRect sourceRect; 

触发我的弹出框的按钮位于 UIToolbar 中。当点击它时,它会运行以下方法来创建和启动弹出框。

-(void) buttonAction:(id)sender event:(UIEvent*)event
{
    NSLog(@"ButtonAction");

    // when the button is tapped we want to display a popover, so setup all the variables needed and present it here

    // get a reference to which button's view was tapped (this is to get 
    // the frame to update the arrow to later on rotation)
    // since UIBarButtonItems don't have a 'frame' property I found this way is easy
    UIView *buttonView          = [[event.allTouches anyObject] view];

    // set our tracker properties for when the orientation changes (handled in the viewWillTransitionToSize method above)
    self.activePopoverBtn       = buttonView;
    self.sourceRect             = buttonView.frame;

    // get our size, make it adapt based on our view bounds
    CGSize viewSize             = self.view.bounds.size;
    CGSize contentSize          = CGSizeMake(viewSize.width, viewSize.height - 100.0);

    // set our popover view controller property
    self.popoverVC = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"PopoverVC"];

    // configure using a convenience method (if you have multiple popovers this makes it faster with less code)
    [self setupPopover:self.popoverVC
        withSourceView:buttonView.superview // this will be the toolbar
            sourceRect:self.sourceRect
           contentSize:contentSize];

    [self presentViewController:self.popoverVC animated:YES completion:nil];

}

'setupPopover:withSourceView:sourceRect:contentSize 方法只是设置 popoverPresentationController 属性的便捷方法,如果您计划显示多个弹出框并希望它们配置相同。它的实现如下。

// convenience method in case you want to display multiple popovers
-(void) setupPopover:(UIViewController*)popover withSourceView:(UIView*)sourceView sourceRect:(CGRect)sourceRect contentSize:(CGSize)contentSize
{
    NSLog(@"\npopoverPresentationController: %@\n", popover.popoverPresentationController);

    popover.modalPresentationStyle = UIModalPresentationPopover;
    popover.popoverPresentationController.delegate = self;
    popover.popoverPresentationController.sourceView                = sourceView;
    popover.popoverPresentationController.sourceRect                = sourceRect;
    popover.preferredContentSize                                    = contentSize;
    popover.popoverPresentationController.permittedArrowDirections  = UIPopoverArrowDirectionDown;
    popover.popoverPresentationController.backgroundColor           = [UIColor whiteColor];
}

对于 iOS 8 及更高版本,viewWillTransitionToSize:withTransitionCoordinator 会在设备旋转时在视图控制器上调用。

我在呈现视图控制器类中实现了这个方法,如下所示。

// called when rotating a device
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    NSLog(@"viewWillTransitionToSize [%@]", NSStringFromCGSize(size));

    // resizes popover to new size and arrow location on orientation change
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context)
    {
        if (self.popoverVC)
        {
            // get the new frame of our button (this is our new source rect)
            CGRect viewframe = self.activePopoverBtn ? self.activePopoverBtn.frame : CGRectZero;

            // update our popover view controller's sourceRect so the arrow will be pointed in the right place
            self.popoverVC.popoverPresentationController.sourceRect = viewframe;

            // update the preferred content size if we want to adapt the size of the popover to fit the new bounds
            self.popoverVC.preferredContentSize = CGSizeMake(self.view.bounds.size.width -20, self.view.bounds.size.height - 100);
        }

    } completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        // anything you want to do when the transition completes
    }];
}
于 2015-11-20T00:22:36.773 回答
0

斯威夫特 3:

    class MyClass: UIViewController, UIPopoverPresentationControllerDelegate {


        ...

        var popover:UIPopoverPresentationController?

        ...

        // Where you want to set the popover...
        popover = YourViewController?.popoverPresentationController
        popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
        popover?.delegate = self

        ...

        // override didRotate...
        override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
          popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
        }

}
于 2017-05-21T03:10:07.227 回答
0

我有 popoverPresentationController,我在具有“假”导航栏的视图上呈现。所以我不能将 popoverPresentationController 附加到 barButtonItem。我的弹出窗口出现在正确的位置,但屏幕旋转时没有出现。

所以由于某种原因popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>)没有得到我的召唤。

为了解决这个问题(iOS 12、Swift 4.2),我在调用 present 时在完成闭包中的弹出窗口中添加了约束。现在我的弹出窗口也停留在我期望的位置。

                present(viewController, animated: true) { [weak self] in
            DDLogDebug(String(describing: viewController.view.frame))
            if let containerView = viewController.popoverPresentationController?.containerView,
            let presentedView = viewController.popoverPresentationController?.presentedView,
            let imageView = self?.headerView.settingsButton {
                withExtendedLifetime(self) {
                    let deltaY:CGFloat = presentedView.frame.origin.y - imageView.frame.maxY
                    let topConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .top, relatedBy: .equal, toItem: imageView.imageView, attribute: .bottom, multiplier: 1, constant: deltaY)
                    topConstraint?.priority = UILayoutPriority(rawValue: 999)
                    topConstraint?.isActive = true
                    let heightContraint = NSLayoutConstraint.init(item: presentedView, attribute: .height, relatedBy: .equal, toItem: containerView, attribute: .height, multiplier: 0.75, constant: -deltaY)
                    heightContraint?.isActive = true
                    let leftConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .left, relatedBy: .equal, toItem: containerView, attribute: .left, multiplier: 1, constant: presentedView.frame.origin.x)
                    leftConstraint.isActive = true
                    let widthConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: presentedView.frame.width)
                    widthConstraint.isActive = true
                    presentedView.translatesAutoresizingMaskIntoConstraints = false
                }
            }
        }
于 2018-10-11T22:30:59.953 回答