提问



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


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

最佳参考


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


一般的答案是,在创建UIViewController时,您必须在UIViewController中保存对UIPopoverController的引用。



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

  2. 如果您使用的是Storyboards和Segues,则可以prepareForSegue方法中的UIPopoverController取出UIPopoverController:


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



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

其它参考1


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


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


// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

@property (assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;

@end


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


// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@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。要使用子类完成此操作,您要么尝试在每个新的子视图控制器上设置它,要么子类化导航控制器,或其他可怕的事情。


更多Runtime Magic



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


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


// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@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/企业/个人构建,或者你足够大胆地看到Apple对App Store的这个想法。无论哪种方式,这里是使用当前运行时和UIViewController 正常工作的实现 - 不需要设置属性!


// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

@property (readonly, assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;

@end





// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@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

其它参考2


最有帮助的可能是使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

其它参考3


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


以下是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
        }
    }
}

其它参考4


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

其它参考5


如果您只想知道您的控制器是否在弹出窗口内呈现(不想获得对弹出控制器的引用),您可以简单地执行此操作,而无需存储变量或攻击私有API。


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