如何使UIPopoverController在旋转后保持相同的位置?

问题描述:

旋转后,屏幕上的相同位置无法保持弹出状态。有没有什么好的方法可以做到这一点,因为在旋转之后,设置一些框架以实现弹出效果非常可怕。 popover.frame = CGRectMake(someFrame);旋转弹出窗口只有在屏幕中心时才会显示正常。如何使UIPopoverController在旋转后保持相同的位置?

+0

只是检查这个环节也.. http://stackoverflow.com/ questions/3670981/adjust-uipopovercontroller-position-after-resize – 2012-01-30 14:24:25

+0

Thanks for:presentPopoverFromRect:inView可用于popover可见时 – 2012-01-30 15:56:10

在这个问题上,Apple有一个Q & A.你可以在这里找到细节:

Technical Q&A QA1694 Handling Popover Controllers During Orientation Changes

基本上,该技术解释说,在您的视图控制器的didRotateFromInterfaceOrientation方法,你将再次出现弹出过如下:

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

欲了解更多信息,阅读上面的文章,还有UIPopoverController Class Reference

如果用户在弹出窗口是vi时旋转设备sible,弹出窗口 控制器隐藏弹出窗口,然后在 轮换结束时再次显示它。弹出窗口控制器尝试为您适当地定位弹出窗口 ,但在某些情况下,您可能需要再次显示或将其隐藏 。例如,当从条形图按钮项目 中显示时,弹出窗口控制器会自动调整弹出窗口的位置 (以及可能的大小),以考虑对条形按钮项目的位置进行更改( )。但是,如果在旋转过程中删除条形图按钮项 ,或者在视图中显示目标矩形的弹出框 ,则弹窗控制器不会尝试 重新定位弹出窗口。在这些情况下,您必须手动隐藏弹出窗口或将其从适当的新位置再次显示。您可以使用 在didRotateFromInterfaceOrientation中执行此操作:您用于呈现弹出窗口的视图 控制器。

您可以在didRotateFromInterfaceOrientation:中使用您用来呈现弹出窗口的视图控制器的方法。

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

+4

这个方法是否改变了popover的起源?我不需要改变popover内容的大小,只是为了保持原点。 – 2012-01-30 14:32:30

我有我这个

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

哪里myfield是要展示你的酥料饼和myscrollview框架解决类似的问题,是在你把你的酥料饼作为子视图容器视图(在我的情况下,其我的scrollview,而不是把inView:self.view我用inView:myscrollview)。

我有同样的问题。我不是每次通过跟踪从中呈现它的源矩形/视图来执行-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]; 

在iOS中7,您可以使用- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view重新定位你的UIPopoverController对接口的方向变化视图。

查看UIPopoverControllerDelegatedocumentation

+0

谢谢,这适用于我在iOS 8 – almas 2015-03-02 19:29:10

从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; 
} 

对于iOS> 8约翰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方法是一个简单的便捷方法,如果你打算显示多个popovers和希望它们配置的相同设置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 GET就是所谓的视图时控制器设备旋转上。

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

// 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 
    }]; 
} 

我试过只是设置新的矩形(rect.initialize(...)),它的工作原理。

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

     if popoverPresentationController.presentedViewController.view.tag == Globals.PopoverTempTag 
     { 
      rect.initialize(getForPopupSourceRect()) 
     } 
    } 

UIPopoverController被弃用ios9赞成UIPopoverPresentationController在iOS8上推出。 (我通过这个转变从UIActionSheetUIAlertController时候去也。)你有两个选择(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; 
} 

斯威夫特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) 
     } 

} 
+0

'didRotate' [已弃用](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621492-didrotate)。您应该使用我在https://stackoverflow.com/a/41561021/954643中提到的两种方法之一,例如'popoverPresentationController(_:willRepositionPopoverTo:in:)'([docs](https://developer.apple.com/documentation/uikit/uipopoverpresentationcontrollerdelegate/1622326-popoverpresentationcontroller?preferredLanguage=swift))这部分是因为您可以更改现在屏幕的布局不止是旋转,比如通过ios9 +中的分屏多任务功能 – qix 2017-06-07 17:24:35

对于斯威夫特:

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 
}