如何从弹出窗口中显示的UIViewController中找到UIPopoverController?

使用UIViewController的实例,有什么方法可以找到用于呈现它的UIPopoverController吗? 我还想找到首先显示UIPopoverController的UIViewController。

我通常会使用委托或其他类型的通知从显示的视图控制器向显示的视图控制器发送信号,但在这种情况下,我正在尝试创建一个可重复使用的自定义segue,解除弹出窗口然后转到另一个视图在主视图中。

你会认为这很简单( UIViewController甚至有一个私有的_popoverController属性!),但事实并非如此。

一般的答案是,在UIPopoverController UIViewControllerUIPopoverControllerUIViewController中保存对UIPopoverController的引用。

  1. 如果以编程方式创建UIPopoverController ,那么就是将引用存储在UIViewController子类中的时间。

  2. 如果您使用的是Storyboard和Segues,则可以从prepareForSegue方法中的segue中获取UIPopoverController

     UIPopoverController* popover = [(UIStoryboardPopoverSegue*)segue popoverController]; 

当然,请确保你的segue真的是一个UIStoryboardPopoverSegue!

我的建议是在UIKit中利用您自己的自定义属性和私有API的组合。 为了避免app store拒绝,任何私有API都应该针对发布版本进行编译,并且应该仅用于检查您的实现。

首先,让我们将自定义属性构建到UIViewController上的类别中。 这允许实现中的一些特权,并且它不需要您返回并从某个自定义视图控制器子类派生每个类。

 // UIViewController+isPresentedInPopover.h #import  @interface UIViewController (isPresentedInPopover) @property (assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover; @end 

现在为实现 – 我们将使用Objective C运行时的关联对象API为此属性提供存储。 请注意,选择器是用于存储对象的唯一键的不错选择,因为它自动由编译器单独使用,并且极不可能被任何其他客户端用于此目的。

 // UIViewController+isPresentedInPopover.m #import "UIViewController+isPresentedInPopover.h" #import  @implementation UIViewController (isPresentedInPopover) - (void)setPresentedInPopover:(BOOL)presentedInPopover { objc_setAssociatedObject(self, @selector(isPresentedInPopover), [NSNumber numberWithBool:presentedInPopover], OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)isPresentedInPopover { NSNumber *wrappedBool = objc_getAssociatedObject(self, @selector(isPresentedInPopover)); BOOL userValue = [wrappedBool boolValue]; return userValue ?: [[self parentViewController] isPresentedInPopover]; } @end 

因此使用它作为类别有一个方便的副作用 – 您可以调用parentViewController并查看它是否也包含在弹出窗口中。 这样你就可以在UINavigationController上设置属性,它的所有子视图控制器都会正确响应isPresentedInPopover 。 要使用子类完成此操作,您要么尝试在每个新的子视图控制器上设置它,要么在子类化导航控制器或其他可怕的东西。

更多运行时魔术

Objective C Runtime还有更多针对这个特定问题的问题,我们可以使用它们跳转到Apple的私有实现细节,并根据它检查自己的应用程序。 对于发布版本,这个额外的代码将编译出来,因此在提交到商店时无需担心Sauron Apple的全视角

你可以从UIViewController.h看到有一个ivar定义为带有@package范围的UIPopoverController* _popoverController 。 幸运的是,这仅由编译器强制执行。 就运行时而言, 没有什么是神圣的,并且从任何地方访问这些ivar都非常容易。 我们将在每次访问属性时添加仅调试运行时检查,以确保我们保持一致。

 // UIViewController+isPresentedInPopover.m #import "UIViewController+isPresentedInPopover.h" #import  @implementation UIViewController (isPresentedInPopover) - (void)setPresentedInPopover:(BOOL)presentedInPopover { objc_setAssociatedObject(self, @selector(isPresentedInPopover), [NSNumber numberWithBool:presentedInPopover], OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)isPresentedInPopover { NSNumber *wrappedBool = objc_getAssociatedObject(self, @selector(isPresentedInPopover)); BOOL userValue = [wrappedBool boolValue]; #if DEBUG Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController"); UIPopoverController *popover = object_getIvar(self, privatePopoverIvar); BOOL privateAPIValue = popover != nil; if (userValue != privateAPIValue) { [NSException raise:NSInternalInconsistencyException format: @"-[%@ %@] " "returning %@ " "while private UIViewController API suggests %@. " "Did you forget to set 'presentedInPopover'?", NSStringFromClass([self class]), NSStringFromSelector(_cmd), userValue ? @"YES" : @"NO", privateAPIValue ? @"YES" : @"NO"]; } #endif return userValue ?: [[self parentViewController] isPresentedInPopover]; } @end 

错误地使用该属性时,您将在控制台上收到如下消息:

2012-09-18 14:28:30.375 MyApp[41551:c07] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Consistency error in -[UINavigationController isPresentedInPopover]: returning NO while private UIViewController API suggests YES. Did you forget to set 'presentedInPopover'?'

…但是在关闭DEBUG标志或设置为0时进行编译时,它会编译为与之前完全相同的代码。

为自由和愚蠢

也许你正在进行Ad-Hoc / Enterprise /个人构建,或者你足够大胆地看到Apple对App Store的这个想法。 无论哪种方式,这是一个使用当前运行时和UIViewController 工作的实现 – 不需要设置属性!

 // UIViewController+isPresentedInPopover.h #import  @interface UIViewController (isPresentedInPopover) @property (readonly, assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover; @end 

 // UIViewController+isPresentedInPopover.m #import "UIViewController+isPresentedInPopover.h" #import  @implementation UIViewController (isPresentedInPopover) - (BOOL)isPresentedInPopover { Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController"); UIPopoverController *popover = object_getIvar(self, privatePopoverIvar); BOOL privateAPIValue = popover != nil; return privateAPIValue ?: [[self parentViewController] isPresentedInPopover]; } @end 

最有帮助的可能是使popover成为一个类变量,因此在将要呈现popover的类的.m文件中,执行以下操作:

  @interface ExampleViewController() @property (nonatomic, strong) UIPopoverController *popover @end @implementation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"some segue"]) { //prevent stacking popovers if ([self.popover isPopoverVisible]) { [self.popover dismissPopoverAnimated:YES]; self.popover = nil; } [segue.destinationViewController setDelegate:self]; self.popover = [(UIStoryboardPopoverSegue *)segue popoverController]; } } @end 

正如@joey在上面所写的那样,Apple在iOS 8中消除了对虚拟控件的需求,其中为UIViewController定义的popoverPresentationController属性为“视图控制器层次结构中最近的祖先,它是一个弹出式表示控制器。(只读)”。

以下是Swift中基于故事板定义的基于UIPopoverPresentationController的segue的示例。 在这种情况下,按钮已经以编程方式添加,并且可以以这种方式定义为弹出窗口的锚点。 发件人也可以是选定的UITableViewCell或来自它的视图。

 override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "showCallout" { let button = sender as UIButton let calloutViewController = segue.destinationViewController as CalloutViewController if let popover = calloutViewController.popoverPresentationController { popover.sourceView = button popover.sourceRect = button.bounds } } } 

从ndoc的anwser起飞: 这个答案在iOS 6中显示了一种更简洁的方式,以防止弹出窗口显示多个时间段。 链接中的方法对我来说非常有效,可以防止弹出堆叠。

如果你只是想知道你的控制器是否出现在一个popover内(不想获得对popover控制器的引用),你可以简单地做到这一点,而不存储变量或者破解私有API。

 -(BOOL)isPresentedInPopover { for (UIView *superview = self.view.superview; superview != nil; superview = superview.superview) { if ([NSStringFromClass([superview class]) isEqualToString:@"_UIPopoverView"]) return YES; } return NO; }