Determine if a view is inside of a Popover view

10,460

Solution 1

I was recently looking for a way to determine wether or not a view was being displayed in a popover. This is what I came up with:

    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");
        }

Basically you climb the view's superview tree looking to see if any of its superviews is a UIPopoverView. The one caveat here is that the class UIPopoverView is an undocumented private class. I'm relying on the fact that the class name won't change in the future. YMMV.

In your case:

theViewInQuestion =  theViewControllerInQuestion.view;

I'd be interested to see if anyone else comes up with a better solution.

Solution 2

As Artem said we have UIPopoverPresentationController since iOS8. To determine if view is in popover you can use its .arrowDirection property for example.

Check it in viewWillApear() of presented view controller:

// 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)
}

Solution 3

Here's another solution; define a protocol (e.g. PopoverSensitiveController) that has only one method:

#import "Foundation/Foundation.h"

@protocol PopoverSensitiveController 
-(void) setIsInPopover:(BOOL) inPopover;
@end

A view controller that wants to know if it is in a popover then defines a property isInPopover; for example:

#import 
#import "PopoverSensitiveController.h"

#pragma mark -
#pragma mark Interface
@interface MyViewController : UIViewController  {
}

#pragma mark -
#pragma mark Properties
@property (nonatomic) BOOL isInPopover;

#pragma mark -
#pragma mark Instance Methods
...other stuff...
@end

Finally, in the splitView delegate (the assumption is that your app uses a split view controller):

#import "MySplitViewControllerDelegate.h"
#import "SubstitutableDetailViewController.h"
#import "PopoverSensitiveController.h"

#pragma mark -
#pragma mark Implementation
@implementation MySplitViewControllerDelegate

#pragma mark -
#pragma mark UISplitViewControllerDelegate protocol methods
-(void) splitViewController:(UISplitViewController *) splitViewController willHideViewController:(UIViewController *) aViewController withBarButtonItem:(UIBarButtonItem *) barButtonItem forPopoverController:(UIPopoverController *) pc {

  // Keep references to the popover controller and the popover button, and tell the detail view controller to show the button
  popoverController = [pc retain];
  popoverButtonItem = [barButtonItem retain];
  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 the view controller wants to know, tell it that it is a popover
  if ([aViewController respondsToSelector:@selector(setIsInPopover:)]) {
    [(id) aViewController setIsInPopover:YES];
  }

  // Make sure the proper view controller is in the popover controller and the size is as requested
  popoverController.contentViewController = aViewController;
  popoverController.popoverContentSize = aViewController.contentSizeForViewInPopover;

}

-(void) splitViewController:(UISplitViewController *) splitViewController willShowViewController:(UIViewController *) aViewController invalidatingBarButtonItem:(UIBarButtonItem *) barButtonItem {

  // Tell the detail view controller to hide the button.
  if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:@selector(invalidateRootPopoverButtonItem:)]) {
    UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
    [detailViewController invalidateRootPopoverButtonItem:barButtonItem];
  }

  // If the view controller wants to know, tell it that it is not in a popover anymore
  if ([aViewController respondsToSelector:@selector(setIsInPopover:)]) {
    [(id) aViewController setIsInPopover:NO];
  }

  // Now clear out everything
  [popoverController release];
  popoverController = nil;
  [popoverButtonItem release];
  popoverButtonItem = nil;

}

-(void) setPopoverButtonForSplitViewController:(UISplitViewController *) splitViewController {

  // Deal with the popover button
  UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
  [detailViewController showRootPopoverButtonItem:popoverButtonItem];

  // If the view controller wants to know, tell it that it is a popover (initialize the controller properly)
  if ([[splitViewController.viewControllers objectAtIndex:0] respondsToSelector:@selector(setIsInPopover:)]) {
    [(id) [splitViewController.viewControllers objectAtIndex:0] setIsInPopover:YES];
  }

}

Then where ever in the view controller you want to know if you are in a popover, simply use the isInPopover property.

Solution 4

In iOS8 you can use popoverPresentationController property of UIViewController to check if it is contained in a popover presentation controller. From documentation, it returns: "The nearest ancestor in the view controller hierarchy that is a popover presentation controller. (read-only)"

Solution 5

Modification of the accepted answer for iOS5.1 and newer:

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");

    }
}

** NOTE **

See comments about the reliability of this code.

Share:
10,460
mjdth
Author by

mjdth

Updated on June 03, 2022

Comments

  • mjdth
    mjdth about 2 years

    We have common views that we use in our application in many locations inside of UINavigationControllers. Occasionally the UINavigationControllers are inside of popover views. Now the views we put into the nav controllers modify their navigation controller's toolbar buttons and, in some cases, use custom buttons that we've created. We need to be able to figure out from the UIViewcontroller itself if the view is inside of a popoverview so we can display the correctly colored buttons.

    We can easily get the Navigation controller reference from the UIViewController, using UIViewController.navigationController, but there doesn't seem to be anything for finding a UIPopoverController.

    Does anyone have any good ideas for how to do this?

    Thanks!

  • mjdth
    mjdth over 13 years
    Sounds like a viable solution, thanks for the feedback! I ended up doing something slightly different and more specific to my app to figure this out, but it looks like this would work as well so I'm going to mark it as the correct answer! Thanks!
  • TJ Seabrooks
    TJ Seabrooks about 13 years
    The problem with this is it only finds you the UIPopoverView... How do you turn that into the controller?
  • Peter DeWeese
    Peter DeWeese almost 13 years
    This is a good solution. isKindOfClass is preferable to a string comparison. I have implemented it in a UIView category as "isInPopover" in my refactoring library, github.com/peterdeweese/es_ios_utils
  • Bart Whiteley
    Bart Whiteley over 11 years
    It is AppStore-safe, but for iOS 5.1 and 6 the classname is now _UIPopoverView.
  • Aaron Brager
    Aaron Brager about 11 years
    Note that if your view is in a UINavigationController, you'll need UIView *v = self.navigationController.view (because self.view.superview is nil in a navigation controller.)
  • n13
    n13 about 11 years
    hard coding an undocumented class name in your code == bad idea.
  • n13
    n13 about 11 years
    hacking the contentsize is a neat idea. why not set it to, say 400.1 and compare with that?
  • n13
    n13 about 11 years
    I am not sure why this is supposed to be good? This way of doing it already broke from iOS 4.x -> 5.1. It'll break again in the future. Maybe this is good if you're looking for perpetual employment but it's not good code.
  • GnarlyDog
    GnarlyDog about 11 years
    I agree. I simply posted as reference for the accepted answer (maybe I shouldn't have? I'll edit my response with a note). My gut is that there was probably a reason to change it, and if so it's likely to be changed again. I ended up using JScarry's contentSize suggestion. I'm betting that as long as the exact size is fractional, it's less likely to break underneath you.
  • GnarlyDog
    GnarlyDog about 11 years
    Although seconds after writing the above I'm thinking that with auto layout it may be risky since the system is dictating size based on constraints.
  • levigroker
    levigroker over 9 years
    This seems to only return a valid object (i.e. non-nil) after the view of the view controller has been displayed. i.e. this returned nil for me from viewWillAppear:
  • JScarry
    JScarry over 9 years
    I’m not actually hacking the content size. I just set the size so that it is big enough to contain all of the elements that I need. If I set it to a non-integer width, then the edges won’t lie on a pixel boundary.
  • arlomedia
    arlomedia about 8 years
    In iOS 9, this works for me in viewWillAppear, but not in viewDidLoad.
  • Michael Tyson
    Michael Tyson about 6 years
    I believe this is the best solution: unlike other proposals, this works correctly on constrained devices when a VC is presented as a popover. On small screens the VC will actually appear full-screen rather than in a popover - this is detected correctly.
  • zaitsman
    zaitsman about 4 years
    Got any source on this?
  • amadour
    amadour about 4 years
    @zaitsman The answer is 4 years old, I don't know how accurate it is now. Last time I checked I think popoverPresentationController was only created if modalPresentationStyle was set to .popover. No source, just my testing.
  • deniz
    deniz almost 3 years
    simply brilliant. checking for existence of popoverPresentationController is not enough. Additionally checking for the arrow direction did the trick. Moreover, this could be further simplified by if self.popoverPresentationController?.arrowDirection == .unknown {...