How to make UIPopoverController keep same position

2020-05-14 18:49发布

I can't keep popover the same position on the screen after rotation. Is there any good way to do that, because just setting some frame to popover works terrible after rotating.popover.frame = CGRectMake(someFrame); After rotation popover looks fine only if it is in the center of the screen.

13条回答
Root(大扎)
2楼-- · 2020-05-14 19:32

As of iOS 8.0.2 willRotateToInterfaceOrientation will not have any effect. As mhrrt mentioned, you need to use the delegate method:

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

So for example if you want your popover to appear directly below a button that was pressed, you would use the following code:

- (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;
}
查看更多
萌系小妹纸
3楼-- · 2020-05-14 19:33

UIPopoverController was deprecated in ios9 in favor of UIPopoverPresentationController introduced in ios8. (I went through this transition also when going from UIActionSheet to UIAlertController.) You have two choices (example in obj-C):

A. Implement the UIViewController method below (UIKit calls this method before changing the size of a presented view controller’s view).

- (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. Alternatively, when configuring your UIPopoverPresentationController to present, also set its delegate. e.g. your presenting vc can implement UIPopoverPresentationControllerDelegate and assign itself as the delegate. Then implement the delegate method:

- (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;
}
查看更多
我想做一个坏孩纸
4楼-- · 2020-05-14 19:37

I've tried just to set new rect (rect.initialize(...)) and it works.

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

        if popoverPresentationController.presentedViewController.view.tag == Globals.PopoverTempTag
        {
            rect.initialize(getForPopupSourceRect())
        }
    }
查看更多
狗以群分
5楼-- · 2020-05-14 19:37

I had a same problem. Instead of performing -presentPopoverFromRect each time by keeping track of the source rectangle / view from which it is presented, I subclassed UIPopoverController. After doing it, all you have to do is set either the UIBarButtonItem / UIView from where the popover has to be displayed. You can even opt for displaying the popover from custom frame which can be passed in as a NSString value.

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

Usage:

If it is a UIBarButtonItem from where you are presenting it:

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

If it is a UIView from where you are presenting the popover:

CSPopoverController *popOver = [[CSPopoverController alloc] initWithContentViewController:navigation];
self.iPadPopoverController = popOver;
[newDateVC setIPadPopoverController:self.iPadPopoverController];
[popOver setPopoverDisplaySource:inButton];
[popOver setPopoverDisplayView:inView];
查看更多
Lonely孤独者°
6楼-- · 2020-05-14 19:41

You can do this in didRotateFromInterfaceOrientation: method of the view controller that you used to present the popover.

Use setPopoverContentSize:animated: method for setting the size of the popover.

查看更多
We Are One
7楼-- · 2020-05-14 19:41

I have similar problem which I resolve by this

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

Where myfield is frame from which you want to show your popover and myscrollview is container view in which you add your popover as subview(in my case its my scrollview, instead of putting inView:self.view I use inView:myscrollview).

查看更多
登录 后发表回答