iOS 8演示控制器如何确定是否真的是弹出窗口

17

我正在使用iOS 8的新自适应“以弹出形式呈现”功能。在StoryBoard中,我连接了一个简单的segue以进行呈现。它在iPhone 6 Plus上作为弹出视图展示得很好,在iPhone 4s上则显示为全屏视图(sheet style)。

问题在于,当以全屏视图显示时,我需要向视图添加一个“完成”按钮,以便可以调用dismissViewControllerAnimated。而当它显示为弹出视图时,我不想显示“完成”按钮。

enter image description here

我尝试查看presentationController和popoverPresentationController的属性,但是找不到任何告诉我它是否实际上被显示为弹出视图的内容。

NSLog( @"View loaded %lx", (long)self.presentationController.adaptivePresentationStyle );          // UIModalPresentationFullScreen
NSLog( @"View loaded %lx", (long)self.presentationController.presentationStyle );                  // UIModalPresentationPopover
NSLog( @"View loaded %lx", (long)self.popoverPresentationController.adaptivePresentationStyle );   // UIModalPresentationFullScreen
NSLog( @"View loaded %lx", (long)self.popoverPresentationController.presentationStyle );           // UIModalPresentationPopover

adaptivePresentationStyle始终返回UIModalPresentationFullScreen,而presentationStyle始终返回UIModalPresentationPopover。

查看UITraitCollection时,我确实发现了一个名为"_UITraitNameInteractionModel"的特性,只有在以Popover形式显示时才设置为1。然而,Apple不通过popoverPresentationController的traitCollection直接提供对该特性的访问。


你找到解决方案了吗? - Bruce
我认为Rob Glassey的回答是最完整的。然而,苹果应该提供一种更简单的方法来完成这个任务。 - Tod Cunningham
@TodCunningham,苹果确实提供了一种简单的方法,请看我的答案。 - malhal
抱歉@malhal,但我的应用是一个游戏,我不想仅仅为了添加一个完成按钮而添加导航控制器。这与游戏的风格不符。 - Tod Cunningham
8个回答

13

我发现最好的方法(气味最少)是使用UIPopoverPresentationControllerDelegate

• 确保被展示的视图控制器被设置为UIPopoverPresentationController的代理,以便管理演示。我正在使用Storyboard,所以在prepareForSegue:中进行设置:

segue.destinationViewController.popoverPresentationController.delegate = presentedVC;

• 在呈现的视图控制器中创建一个属性来跟踪这个状态:

@property (nonatomic, assign) BOOL amDisplayedInAPopover;

• 并添加以下委托方法(或将其添加到现有的委托方法中):

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
    // This method is only called if we are presented in a popover
    self.amDisplayedInAPopover = YES;
}

• 最后,在viewWillAppear:方法中 - viewDidLoad:方法太早了,委托准备方法在viewDidLoad:viewWillAppear:之间被调用。

if (self.amDisplayedInAPopover) {
    // Hide the offending buttons in whatever manner you do so
    self.navigationItem.leftBarButtonItem = nil;
}

编辑:更简单的方法!

只需设置委托(确保您的 presentedVC 采用 UIPopoverPresentationControllerDelegate):

segue.destinationViewController.popoverPresentationController.delegate = presentedVC;

并提供该方法:

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
    // This method is only called if we are presented in a popover
    // Hide the offending buttons in whatever manner you do so
    self.navigationItem.leftBarButtonItem = nil;
}

1
segue.destinationViewController 是开始查找的地方。如果在您认为正在呈现的视图控制器之前有一个中介导航控制器,请注意,目标 VC 将是导航控制器。 - Rob Glassey
尝试了这里的所有其他解决方案,但只有这个是有效的且气味最小。谢谢! - Bruce
是的,topViewController 确实比我过去使用的 interveningNavigationController.viewControllers.lastObject 好看多了! - Rob Glassey
1
你也可以直接在委托方法中隐藏按钮。无需跟踪状态,或修改viewWillAppear:,除非有可能在弹出窗口之外再次呈现相同的实例(很少,除非您缓存并重复使用它)。 - Rob Glassey
1
请注意,此方法不适用于自适应大小类。每当全屏变为弹出窗口时,都会调用prepareForPopoverPresentation方法,但反之则不然。 - Frederik Winkelsdorf
显示剩余2条评论

10

在视图布局完成后,我会检查popoverPresentationController的arrowDirection是否已设置。对于我的需求来说,这足够好,并且涵盖了小屏幕设备上弹出式窗口的情况。

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    if (popoverPresentationController?.arrowDirection != UIPopoverArrowDirection.Unknown) {
        // This view controller is running in a popover
        NSLog("I'm running in a Popover")
    }
}

自从这个答案被写出来以后,你有没有找到更好的方法? - SAHM

3

官方实现此功能的方法是先从您的视图控制器中删除“完成”按钮,然后在适应紧凑模式时将您的视图控制器嵌入导航控制器中,并将“完成”按钮添加为导航项:

func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
    return UIModalPresentationStyle.FullScreen
}

func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
    let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
    let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
    navigationController.topViewController.navigationItem.rightBarButtonItem = btnDone
    return navigationController
}

func dismiss() {
    self.dismissViewControllerAnimated(true, completion: nil)
}

完整教程

截图


这是一篇关于演示控制器的教程。截图如上所示。

3
我测试了这篇文章中提出的所有解决方案,很抱歉,没有一个能在所有情况下正确工作。例如,在iPad的分割视图样式中,拖动分割视图线时可能会更改演示样式,因此我们需要特定的通知来处理这种情况。
经过几个小时的研究,我在苹果的示例代码(Swift)中找到了解决方案: https://developer.apple.com/library/ios/samplecode/AdaptivePhotos/Introduction/Intro.html#//apple_ref/doc/uid/TP40014636 以下是同样的解决方案,但使用了obj-c语言。
首先,在prepareForSegue函数中设置popoverPresentationController委托。它也可以在MyViewController "init"中设置,但不能在"viewDidLoad"中设置(因为会在viewDidLoad之前调用willPresentWithAdaptiveStyle)。
MyViewController *controller = [segue destinationViewController];
        controller.popoverPresentationController.delegate = (MyViewController *)controller;

现在,每当iOS更改呈现样式(包括首次呈现)时,MyViewController对象将接收此通知。以下是示例实现,它在导航控制器中显示/隐藏“关闭”按钮:
- (void)presentationController:(UIPresentationController *)presentationController
  willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style
         transitionCoordinator:(nullable id<UIViewControllerTransitionCoordinator>)transitionCoordinator {
    if (style == UIModalPresentationNone) {
        // style set in storyboard not changed (popover), hide close button
        self.topViewController.navigationItem.leftBarButtonItem = nil;
    } else {
        // style changed by iOS (to fullscreen or page sheet), show close button
        UIBarButtonItem *closeButton =
            [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(closeAction)];
        self.topViewController.navigationItem.leftBarButtonItem = closeButton;
    }
}

- (void)closeAction {
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}

3
如何呢?
if (self.modalPresentationStyle == UIModalPresentationPopover)

对我起作用了


2

适用于多任务处理的解决方案

将呈现控制器指定为浮动视图的委托。

...
controller.popoverPresentationController.delegate = controller;
[self presentViewController:controller animated:YES completion:nil];

接着,在控制器中实现代理方法:

- (void)presentationController:(UIPresentationController *)presentationController willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style transitionCoordinator:(id<UIViewControllerTransitionCoordinator>)transitionCoordinator
{
    if (style != UIModalPresentationNone)
    {
        // Exited popover mode
        self.navigationItem.leftBarButtonItem = button;
    }
}

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
    // Entered popover mode
    self.navigationItem.leftBarButtonItem = nil;
}

2
管理您的视图控制器的UIPresentationController通过将modalPresentationStyle设置为UIModalPresentationPopover来呈现它。
根据UIViewController 参考文献
“presentingViewController” - 呈现此视图控制器的视图控制器。(只读)
“modalPresentationStyle” - UIModalPresentationPopover:在水平正常环境下,使用弹出视图显示内容的呈现样式。背景内容被调暗,点击弹出窗口外部会使弹出窗口消失。如果您不希望点击消失弹出窗口,则可以将一个或多个视图分配给相关的UIPopoverPresentationController对象的passthroughViews属性,该对象可以从popoverPresentationController属性中获取。
因此,我们可以通过检查horizontalSizeClass来确定您的视图控制器是在弹出窗口内还是以模态方式呈现(我假设您的按钮是一个UIBarButtonItem)。
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    if (self.presentingViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
        self.navigationItem.leftBarButtonItem = nil; // remove the button
}

最安全的检查位置是在 viewWillAppear: 中,否则 presentingViewController 可能会是 nil


你不能从水平大小类推断出你是否在弹出窗口中。iPhone上的视图控制器也有常规的水平大小类。 - Frederic Adda
@FredA. 请查看WWDC 2014会议第216节@4:24。除了iPhone 6 Plus之外,所有iPhone都具有水平和垂直方向上的紧凑尺寸类别。在我的一个项目中,我使用“作为弹出窗口”自适应segue来呈现弹出窗口控制器,这个发布的解决方案在那里运行良好。它对你不起作用吗? - Tomas Camin
1
这可能可行,但与检查userInterfaceIdiom是否为iPad相同。此外,您甚至可以在iPhone上强制显示内容在弹出窗口中。因此,我认为问题是关于特别检查内容是否以popover的形式呈现。 - Frederic Adda
不行,在iPhone 6Plus横屏模式下无法使用。当使用“作为弹出窗口呈现”自适应转场时,弹出窗口将根据水平大小类别在弹出窗口或模态方式下呈现,如引用文档中所述。 - Tomas Camin
2
我理解你的观点。如果在紧凑的水平尺寸下有一个弹出窗口,它将被呈现为模态全屏。这是默认行为。现在,请查看Ray Wenderlich的“iOS 8 by tutorials”第6章,您将看到即使在纵向iPhone上也可以强制使用弹出窗口(并且完全合法)。在这种情况下,您如何知道必须删除栏按钮项?我只是说您提出的解决方案过于依赖大小类,而不是检查我们实际上是否处于弹出窗口中。 - Frederic Adda

1
我是一个有用的助手,可以翻译文本。

我的巧妙解决方案,完美地运作。

PopoverViewControllerviewDidLoad 方法中。

if (self.view.superview!.bounds != UIScreen.main.bounds) {
    print("This is a popover!")
}

这个想法很简单,弹出窗口的视图大小通常不等于设备屏幕大小,除非它不是一个弹出窗口。

这对我来说完美地运作了,但必须在viewDidAppear中进行检查。 - Chuck Boris
在我的情况下,我需要检查视图的边界而不是父视图的边界,并在viewWillAppear或之后进行检查。Objective-C:bool isFullScreen = CGRectEqualToRect(self.view.bounds, [UIScreen mainScreen].bounds); - jk7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接