iOS8, Popovers & PassthroughViews


When presenting a popover you can set the passthroughViews property to control which views that can receive touches while the popover is visible. Tapping on views that are not included will cause the popover to dismiss.

When presenting a popover from a UIBarButtonItem iOS adds some views by default to the passthroughViews property (see discussion below.).

To present a popover without any passthroughViews on iOS7 you could do the following:

UIPopoverController *popoverController = ...
[popoverController presentPopoverFromBarButtonItem:barButtonItem permittedArrowDirections:arrowDirections animated:animated];
                                                                   
// Needs to be set after presentation to take effect.     
popoverController.passthroughViews = nil;

This no longer works on iOS 8 (up to 8.1.1 at the time of writing). The passthroughViews assignment is ignored / overwritten.

Well on iOS8 you should actually use the new UIPopoverPresentationController to present a popover instead. Let’s see if that works.

viewController.modalPresentationStyle = UIModalPresentationPopover;

UIPopoverPresentationController *presentationController = viewController.popoverPresentationController;
presentationController.barButtonItem = sender;
presentationController.passthroughViews = nil;
presentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;

[self presentViewController:viewController animated:YES completion:NULL];

No, did not work either. Touches goes through to other bar buttons on the same toolbar without the popover dismissing. Trying to set passthroughViews on the next line after presenting the view controller does not help either as it did on iOS7 with the UIPopoverController.

Turns out waiting even longer than just the line below the presentation gets it working on iOS 8.

UIPopoverController *popoverController = ...
[popoverController presentPopoverFromBarButtonItem:barButtonItem permittedArrowDirections:arrowDirections animated:animated];

/// Wait 0.1 sec.                                                                   
int64_t delta = (int64_t)(1.0e9 * 0.1);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delta), dispatch_get_main_queue(), ^{
  popoverController.passthroughViews = nil;	
});

With UIPopoverPresentationController on iOS8 it gets a bit more elegant since we have a completion handler:

viewController.modalPresentationStyle = UIModalPresentationPopover;

UIPopoverPresentationController *presentationController = viewController.popoverPresentationController;
presentationController.barButtonItem = sender;
presentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;

[self presentViewController:viewController animated:YES completion:^{
  presentationController.passthroughViews = nil;
}];

Discussion


When presenting the popover providing a view and a frame instead of a bar button this issue with the passthroughViews works as expected. The passthroughViews can be set before presenting the popover and certainly without any delay. The reason could be that in this case the passthroughViews property is not modified by default.

The docs on presenting from a bar button clearly states that the passthroughViews property is tampered with.

Apple doc for UIPopoverController method presentPopoverFromBarButtonItem:permittedArrowDirections:animated:

When presenting the popover, this method adds the toolbar that owns the button to the popover’s list of passthrough views.

For the new iOS8 UIPopoverPresentationController’s barButtonItem property the Apple doc states:

Prior to presentation, the presentation controller adds all sibling bar button items of the specified item (but not the item itself) to the popover’s list of passthrough views.

The difference is that with UIPopoverController the whole UINavigationBar/UIToolbar that the bar button item belongs to is added by default as a passthrough view and with UIPopoverPresentationController only the actual buttons on the same UINavigationBar/UIToolbar as the bar button item are added (so tapping between buttons dismisses the popover).

Both documentations then instructs:

If you want taps in the other bar button items to dismiss the popover, you must add code to the action handlers of those items to do so yourself.

Are they actually prohibiting the use of passthroughViews when presenting from a bar button?
Much easier to set the passthroughViews to nil, right?

However, the HIG states:

When possible, allow people to close one popover and open a new one with one tap. This behavior is especially desirable when several different bar buttons each open a popover, because it prevents people from having to make extra taps.

So if there are other bar button items on the same toolbar that also presents a popover, setting the passthroughViews to nil is not suitable as a fast fix because it will force 2 taps to open the next popover.

Links


Possibly related radar