20

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

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

4

6 回答 6

25

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

一般的答案是,您必须在创建时保存对UIPopoverController它所UIViewController呈现的 的引用UIViewController

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

  2. 如果您正在使用 Storyboards 和 Segue,则可以UIPopoverController在方法中摆脱 segue prepareForSegue

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

当然,请确保您的 segue 确实是 UIStoryboardPopoverSegue!

于 2012-06-02T05:49:43.567 回答
11

我的建议是结合使用您自己的自定义属性和 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

因此,将其用作类别有一个方便的副作用 - 您可以调用 theparentViewController并查看它是否也包含在弹出框中。这样您就可以在 a 上设置属性,UINavigationController并且它的所有子视图控制器都将正确响应isPresentedInPopover. 要使用子类完成此操作,您要么尝试在每个新的子视图控制器上设置它,要么将导航控制器子类化,或其他可怕的事情。

更多运行时魔法

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

从中可以看出UIViewController.h,有一个 ivar 定义为UIPopoverController* _popoverControllerwith @packagescope。幸运的是,这仅由编译器强制执行。就运行时而言,没有什么是神圣的,从任何地方访问该 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/Enterprise/personal build,或者你足够大胆地看到 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
于 2012-09-18T18:53:00.790 回答
1

正如@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
        }
    }
}
于 2014-11-22T12:12:17.983 回答
1

最有帮助的可能是使 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
于 2012-09-06T02:38:09.613 回答
0

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

于 2013-08-08T09:13:35.860 回答
-1

If you JUST want to know if your controller is being presented inside a popover (not interested to get a reference to the popover controller), you can simply do this, without storing variables nor hacking private API's.

-(BOOL)isPresentedInPopover
{
    for (UIView *superview = self.view.superview; superview != nil; superview = superview.superview)
    {
        if ([NSStringFromClass([superview class]) isEqualToString:@"_UIPopoverView"])
            return YES;
    }
    return NO;
}
于 2013-08-14T21:36:52.870 回答