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

Sof*_*mes 19 popover ipad ios

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

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

Chr*_*phe 25

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

一般的答案是,你必须保存到一个参考UIPopoverControllerUIViewController,它是展示,在时间UIViewController被创建.

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

  2. 如果您使用的是Storyboards和Segues,则可以通过方法获取UIPopoverControllersegue prepareForSegue:

    UIPopoverController* popover = [(UIStoryboardPopoverSegue*)segue popoverController];
    
    Run Code Online (Sandbox Code Playgroud)

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

  • @OdedBenDov哇预测很好.它已经到了iOS 8 - "popoverPresentationController". (3认同)
  • 哇.谢谢你的理智!其他人 - 忽略上面所有复杂的理论,只需使用它! (2认同)

Ste*_*her 11

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

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

// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

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

@end
Run Code Online (Sandbox Code Playgroud)

现在进行实现 - 我们将使用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
Run Code Online (Sandbox Code Playgroud)

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

更多运行时魔术

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

你可以看到UIViewController.h,有定义为伊娃UIPopoverController* _popoverController@package范围.幸运的是,这仅由编译器强制执行.就运行时而言,没有什么是神圣的,并且从任何地方访问这些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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)
// 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
Run Code Online (Sandbox Code Playgroud)

  • 访问私有变量的一种更简单的方法是`[self valueForKey:@"_ popoverController"]`.这是因为`+ [UIViewController accessInstanceVariablesDirectly]`返回`YES`.由于我的答案中的方法仅取决于头文件中的规范而不是`accessInstanceVariablesDirectly`将要返回的实现细节,我认为运行时方法是更好的选择.如果您不同意,请随意使用`UIPopoverController*popover = [self valueForKey:@"_ popoverController"]; (3认同)