【问题标题】:Determine if a view is inside of a Popover view确定视图是否在 Popover 视图内
【发布时间】:2025-12-24 14:20:13
【问题描述】:

我们在UINavigationControllers 内的许多位置都有我们在应用程序中使用的共同视图。有时UINavigationControllers 在弹出视图中。现在,我们放入导航控制器的视图修改了导航控制器的工具栏按钮,并且在某些情况下,使用了我们创建的自定义按钮。我们需要能够从 UIViewcontroller 本身判断视图是否在弹出视图中,以便我们可以显示正确颜色的按钮。

使用UIViewController.navigationController,我们可以轻松地从UIViewController 中获取导航控制器引用,但似乎没有任何东西可以找到UIPopoverController

有人对如何做到这一点有任何好的想法吗?

谢谢!

【问题讨论】:

    标签: objective-c ipad uipopovercontroller


    【解决方案1】:

    正如 Artem 所说,自 iOS8 以来我们就有 UIPopoverPresentationController。例如,要确定视图是否在弹出窗口中,您可以使用其.arrowDirection 属性。

    在显示的视图控制器的viewWillApear() 中检查它:

    // get it from parent NavigationController
    UIPopoverPresentationController* popoverPresentationVC = self.parentViewController.popoverPresentationController; 
    if (UIPopoverArrowDirectionUnknown > popoverPresentationVC.arrowDirection) {
    // presented as popover
    } else {
    // presented as modal view controller (on iPhone)
    }
    

    【讨论】:

    • 我相信这是最好的解决方案:与其他提议不同,当 VC 以弹出框的形式呈现时,它可以在受限设备上正常工作。在小屏幕上,VC 实际上会全屏显示,而不是弹出框 - 这是正确检测到的。
    • 简直太棒了。检查popoverPresentationController 的存在是不够的。另外检查箭头方向也可以解决问题。此外,这可以通过if self.popoverPresentationController?.arrowDirection == .unknown {... 进一步简化
    【解决方案2】:

    这是另一个解决方案;定义一个只有一个方法的协议(例如 PopoverSensitiveController):

    #import "基础/Foundation.h" @protocol PopoverSensitiveController -(void) setIsInPopover:(BOOL) inPopover; @结尾

    想要知道它是否在弹出窗口中的视图控制器然后定义一个属性 isInPopover;例如:

    #进口 #import "PopoverSensitiveController.h" #pragma 标记 - #pragma 标记接口 @interface MyViewController : UIViewController { } #pragma 标记 - #pragma 标记属性 @property (nonatomic) BOOL isInPopover; #pragma 标记 - #pragma mark 实例方法 ...其他的东西... @结尾

    最后,在 splitView 委托中(假设您的应用使用了拆分视图控制器):

    #import "MySplitViewControllerDelegate.h" #import "可替换的DetailViewController.h" #import "PopoverSensitiveController.h" #pragma 标记 - #pragma mark 实现 @implementation MySplitViewControllerDelegate #pragma 标记 - #pragma mark UISplitViewControllerDelegate 协议方法 -(void) splitViewController:(UISplitViewController *) splitViewController willHideViewController:(UIViewController *) aViewController withBarButtonItem:(UIBarButtonItem *) barButtonItem forPopoverController:(UIPopoverController *) pc { // 保留对弹出框控制器和弹出框按钮的引用,并告诉细节视图控制器显示按钮 popoverController = [pc 保留]; popoverButtonItem = [barButtonItem 保留]; if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:@selector(showRootPopoverButtonItem:)]) { UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController showRootPopoverButtonItem:barButtonItem]; } if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:@selector(showRootPopoverButtonItem:)]) { UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController showRootPopoverButtonItem:barButtonItem]; } // 如果视图控制器想知道,告诉它它是一个弹出框 if ([aViewController respondsToSelector:@selector(setIsInPopover:)]) { [(id) aViewController setIsInPopover:YES]; } // 确保正确的视图控制器在弹出框控制器中,并且尺寸符合要求 popoverController.contentViewController = aViewController; popoverController.popoverContentSize = aViewController.contentSizeForViewInPopover; } -(void) splitViewController:(UISplitViewController *) splitViewController willShowViewController:(UIViewController *) aViewController invalidatingBarButtonItem:(UIBarButtonItem *) barButtonItem { // 告诉细节视图控制器隐藏按钮。 if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:@selector(invalidateRootPopoverButtonItem:)]) { UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController invalidateRootPopoverButtonItem:barButtonItem]; } // 如果视图控制器想知道,告诉它它不再在弹出窗口中 if ([aViewController respondsToSelector:@selector(setIsInPopover:)]) { [(id) aViewController setIsInPopover:NO]; } // 现在清除所有内容 [popoverController 发布]; 弹出控制器=无; [popoverButtonItem 发布]; popoverButtonItem = 无; } -(void) setPopoverButtonForSplitViewController:(UISplitViewController *) splitViewController { // 处理popover按钮 UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController showRootPopoverButtonItem:popoverButtonItem]; // 如果视图控制器想知道,告诉它它是一个弹出框(正确初始化控制器) if ([[splitViewController.viewControllers objectAtIndex:0] respondsToSelector:@selector(setIsInPopover:)]) { [(id) [splitViewController.viewControllers objectAtIndex:0] setIsInPopover:YES]; } }

    然后,您想知道视图控制器中的任何位置是否在弹出窗口中,只需使用 isInPopover 属性即可。

    【讨论】:

      【解决方案3】:

      在 iOS8 中,您可以使用 UIViewController 的 popoverPresentationController 属性来检查它是否包含在弹出显示控制器中。从文档中,它返回:“视图控制器层次结构中最近的祖先是一个弹出显示控制器。(只读)”

      【讨论】:

      • 这似乎只在视图控制器的视图显示后返回一个有效对象(即非零)。即这从viewWillAppear:为我返回了nil
      • 在 iOS 9 中,这适用于 viewWillAppear,但不适用于 viewDidLoad。
      【解决方案4】:

      我最近正在寻找一种方法来确定视图是否显示在弹出窗口中。这是我想出的:

          UIView *v=theViewInQuestion;        
          for (;v.superview != nil; v=v.superview) {
              if (!strcmp(object_getClassName(v), "UIPopoverView")) {
                  NSLog(@"\n\n\nIM IN A POPOVER!\n\n\n\n");
              }
      

      基本上你爬上视图的超级视图树来查看它的任何超级视图是否是 UIPopoverView。这里需要注意的是 UIPopoverView 类是一个未记录的私有类。我依赖于类名将来不会改变的事实。 YMMV。

      在你的情况下:

      theViewInQuestion =  theViewControllerInQuestion.view;
      

      我很想看看其他人是否提出了更好的解决方案。

      【讨论】:

      • 听起来是一个可行的解决方案,感谢您的反馈!我最终对我的应用程序做了一些稍微不同和更具体的事情来解决这个问题,但看起来这也可以,所以我将其标记为正确答案!谢谢!
      • 这个问题是它只找到你的 UIPopoverView...你如何把它变成控制器?
      • 这是一个很好的解决方案。 isKindOfClass 优于字符串比较。我在我的重构库github.com/peterdeweese/es_ios_utils 中将它实现为 UIView 类别中的“isInPopover”
      • 它是 AppStore 安全的,但对于 iOS 5.1 和 6,类名现在是 _UIPopoverView。
      • 在代码中硬编码未记录的类名 == 坏主意。
      【解决方案5】:

      iOS5.1 及更高版本接受答案的修改:

      for (UIView *v = self.view; v.superview != nil; v=v.superview) { 
      
          if ([v isKindOfClass:[NSClassFromString(@"_UIPopoverView") class]]) {
      
              NSLog(@"\n\n\nIM IN A POPOVER!\n\n\n\n");
      
          }
      }
      

      ** 注意 **

      请参阅 cmets 了解此代码的可靠性。

      【讨论】:

      • 请注意,如果您的视图位于 UINavigationController 中,则需要 UIView *v = self.navigationController.view(因为 self.view.superview 在导航控制器中是 nil。)
      • 我不知道为什么这应该是好的?这种方式已经从 iOS 4.x -> 5.1 中打破了。以后又要破了。如果您正在寻找永久工作,也许这很好,但它不是好的代码。
      • 我同意。我只是发布作为已接受答案的参考(也许我不应该有?我会用注释编辑我的回复)。我的直觉是,可能有理由改变它,如果是这样,它很可能会再次改变。我最终使用了 JScarry 的 contentSize 建议。我敢打赌,只要确切的大小是分数,它就不太可能在你下面折断。
      • 虽然在写完上述内容几秒钟后,我认为使用自动布局可能会有风险,因为系统会根据约束来决定大小。
      【解决方案6】:

      我的做法:(适用于 iOS 8 或更高版本)

      - (BOOL)isContainedInPopover
      {
          UIPopoverPresentationController* popoverPresentationVC = self.parentViewController.popoverPresentationController;
          return (popoverPresentationVC != nil);
      }
      

      父视图控制器将是导航控制器,如果在弹出框内,它将具有非零popoverPresentationController 属性。

      【讨论】:

        【解决方案7】:

        通过使用 SpareTime 的代码,我得到了这个,它按预期工作。不错的代码,不错的解决方案:

        使用标准的 UISplitViewController 示例。

        /* MasterViewController.h */
        
        #import "UIPopoverViewDelegate.h"
        
        @interface masterViewController : UITableViewController <UIPopoverViewDelegate>
        @property (nonatomic) BOOL isInPopover;
        @end
        

        /* MasterViewController.m */
        
        #import "MasterViewController.h"
        
        @implementation MasterViewController
        
        @synthesize isInPopover = _isInPopover;
        
        - (void)viewWillAppear:(BOOL)animated {
            [super viewWillAppear:animated];
        
            if (self.isInPopover)
            {
                // Code for appearing in popover
            }
            else
            {
                // Code for not appearing in popover
            }
        }
        
        @end
        

        /* DetailViewController.h */
        
        #import "UIPopoverViewDelegate.h"
        
        @interface detailViewController : UIViewController <UISplitViewControllerDelegate>
        @end
        

        /* DetailViewController.m */
        
        #import "DetailViewController.h"
        
        @implementation detailViewController
        
        - (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
        {
        
            /* This method is called when transitioning to PORTRAIT orientation. */
        
            UIViewController *hiddenViewController = [(UINavigationController *)viewController childViewControllers].lastObject;
        
            if ([hiddenViewController respondsToSelector:@selector(setIsInPopover:)])
                [(id <UIPopoverViewDelegate>)hiddenViewController setIsInPopover:YES];
        }
        
        - (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
        {
        
            /* This method is called when transitioning to LANDSCAPE orientation. */
        
            UIViewController *shownViewController = [(UINavigationController *)viewController childViewControllers].lastObject;
        
            if ([shownViewController respondsToSelector:@selector(setIsInPopover:)])
                [(id <UIPopoverViewDelegate>)shownViewController setIsInPopover:NO];
        }
        
        @end
        

        /* UIPopoverViewDelegate.h */
        
        @protocol UIPopoverViewDelegate
        @required
        -(void)setIsInPopover:(BOOL)inPopover;
        @end
        

        【讨论】:

          【解决方案8】:

          如果其他人仍在寻找解决方案,我想出了一个对我来说足够好的解决方案。

          只需重写这个方法

          func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
          
              (controller.presentedViewController as? YourViewControler).isPopover = false
          
              return controller.presentedViewController
          }
          

          这是 YourViewController 的示例

          class AdvisorHomeFilterViewController: UIViewController {
          
              // MARK: - Properties
          
              var isPopover = true
          }
          

          如果是popover,它不会调用'viewControllerForAdaptivePresentationStyle'方法,它会保持true,如果不是popover,它会设置为false。

          【讨论】:

            【解决方案9】:

            如果视图没有显示在弹出窗口中,我想在视图中放置一个按钮。我知道弹出框的宽度,因为我只是设置了它。所以我可以测试我是否在 iPad 上,以及框架的宽度是否与我设置的相同。

            - (void)viewWillAppear:(BOOL)animated {
            [self setContentSizeForViewInPopover:CGSizeMake(400, 500)];
            
            NSInteger frameWidth = self.view.frame.size.width;
            //Let you go back to the game if on an iPod.
            if ( ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) && !(frameWidth == 400) ) { ---code to display a button --}
            

            【讨论】:

            • 破解 contentsize 是个好主意。为什么不将其设置为 400.1 并与之进行比较?
            • 我实际上并没有破解内容大小。我只是设置了大小,使其足够大以包含我需要的所有元素。如果我将其设置为非整数宽度,则边缘不会位于像素边界上。
            【解决方案10】:

            所有这些“精确的类名匹配方法”都非常容易失败,即使 Apple 做出最轻微的更改也会崩溃。做单字符变量和神秘的 for 循环也不完全符合我的风格。

            我使用以下代码:

            - (BOOL) isInPopOver {
                UIView *currentView = self.view;
                while( currentView ) {
                    NSString *classNameOfCurrentView = NSStringFromClass([currentView class]);
                    NSLog( @"CLASS-DETECTED: %@", classNameOfCurrentView );
                    NSString *searchString = @"UIPopoverView";
                    if( [classNameOfCurrentView rangeOfString:searchString options:NSCaseInsensitiveSearch].location != NSNotFound ) {
                        return YES;
                    }
                    currentView = currentView.superview;
                }
                return NO;
            }
            

            【讨论】:

              【解决方案11】:

              上述所有解决方案似乎都有点复杂。我正在使用一个名为isInPopover 的变量,如果视图控制器出现在弹出窗口中,我将其设置为true。在popoverControllerDidDismissPopoverviewWillDisappear 的视图控制器中,我将布尔值设置为false。它确实有效,而且非常简单。

              【讨论】:

                【解决方案12】:

                由于 self.popoverPresentationController 在最新的 iOS 版本中是惰性创建的,因此应检查 self.popoverPresentationController.presentingViewController 是否为 nil,如果不是 nil,则表示 self 当前出现在弹出窗口中。

                【讨论】:

                • 有这方面的资料吗?
                • @zaitsman 答案是4岁,不知道现在有多准确。上次我检查时,我认为只有在 modalPresentationStyle 设置为 .popover 时才会创建 popoverPresentationController。没有来源,只是我的测试。
                【解决方案13】:

                Swift 4 版本(功能可在extension UIViewController添加):

                func isInPopover() -> Bool {
                    guard UIDevice.current.userInterfaceIdiom == .pad else { return false }
                
                    var checkingVC: UIViewController? = self
                    repeat {
                        if checkingVC?.modalPresentationStyle == .popover {
                            return true
                        }
                        checkingVC = checkingVC?.parent
                    } while checkingVC != nil
                    return false
                }
                

                【讨论】: