Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>My recommendation is to leverage a combination of your own custom property and the private APIs in UIKit. To avoid app store rejection, any private APIs should compile out for release builds, and should be used only to check against your implementation.</p> <p>First let's build the custom property into a category on <code>UIViewController</code>. This allows some perks in the implementation, and it doesn't require you to go back and derive every class from some custom view controller subclass.</p> <pre><code>// UIViewController+isPresentedInPopover.h #import &lt;UIKit/UIKit.h&gt; @interface UIViewController (isPresentedInPopover) @property (assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover; @end </code></pre> <p>Now for the implementation - we'll be using the Objective C runtime's associated object API to provide the storage for this property. Note that a selector is a nice choice for the unique key used to store the object, as it's automatically uniqued by the compiler and highly unlikely to be used by any other client for this purpose.</p> <pre><code>// UIViewController+isPresentedInPopover.m #import "UIViewController+isPresentedInPopover.h" #import &lt;objc/runtime.h&gt; @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 </code></pre> <p>So there's a convenient side effect of using this as a category - you can call up to the <code>parentViewController</code> and see if that is contained in a popover as well. This way you can set the property on, say, a <code>UINavigationController</code> and all of its child view controllers will respond correctly to <code>isPresentedInPopover</code>. To accomplish this with subclasses, you'd be either trying to set this on every new child view controller, or subclassing navigation controllers, or other horrific things.</p> <h2>More Runtime Magic</h2> <p>There is still more that the Objective C Runtime has to offer for this particular problem, and we can use them to jump into Apple's private implementation details and check your own app against it. For release builds, this extra code will compile out, so no need to worry about the all-seeing eye of <strike>Sauron</strike> Apple when submitting to the store.</p> <p>You can see from <code>UIViewController.h</code> that there is an ivar defined as <code>UIPopoverController* _popoverController</code> with <code>@package</code> scope. Luckily this is only enforced by the compiler. <em>Nothing</em> is sacred as far as the runtime is concerned, and it's pretty easy to access that ivar from anywhere. We'll add a debug-only runtime check on each access of the property to make sure we're consistent.</p> <pre><code>// UIViewController+isPresentedInPopover.m #import "UIViewController+isPresentedInPopover.h" #import &lt;objc/runtime.h&gt; @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 </code></pre> <p>When using the property incorrectly, you'll get a message like this on the console:</p> <blockquote> <p><code>2012-09-18 14:28:30.375 MyApp[41551:c07] <em>*</em> 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'?'</code></p> </blockquote> <p>...but when compiling with the DEBUG flag off or set to 0, it compiles down to the exact same code as before.</p> <h2>For The Free and the Foolhardy</h2> <p>Maybe you're doing Ad-Hoc/Enterprise/personal builds, or you're sufficiently bold to see just what Apple thinks about this one for the App Store. Either way, here's an implementation that <em>just works</em> using the current runtime and <code>UIViewController</code> - no setting properties needed!</p> <pre><code>// UIViewController+isPresentedInPopover.h #import &lt;UIKit/UIKit.h&gt; @interface UIViewController (isPresentedInPopover) @property (readonly, assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover; @end </code></pre> <hr> <pre><code>// UIViewController+isPresentedInPopover.m #import "UIViewController+isPresentedInPopover.h" #import &lt;objc/runtime.h&gt; @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 </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload