I'm having an issue where my UINavigationController
's default pop animation has unexpected behavior - the popped controller sometimes jumps off-screen left or right.
The issue seems related to overriding the controller's UITraitCollection
.
I have a universal app, and on iPad, a custom UIPresentationController
to display a nav in a partial modal, where its width is a fraction of the screen width. Thus, I override the horizontalSizeClass
to compact on the UIPresentationController
's overrideTraitCollection
property, so all controllers presented in this "half modal" assume their iPhone layout.
Overriding that size class seems to trigger the bug. Suddenly, when popping a controller within that "half modal", the animation is messed up in landscape (it either jumps left or right).
Here's an example of what it looks like:
Attempts
First, when I get rid of the traitCollection
override, the bug goes away. Obviously though, I want to override the horizontal size class, because these views are reused elsewhere in regular environments too.
Thus, I tried overriding the horizontalSizeClass
of the modal's children in other ways, like:
UINavigationControllerDelegate
to override
each child's traitCollection
on
navigationController:didShowViewController:animated:
– this seemed to make no differencetraitCollection
before it pushes itLike so:
[self.navigationController setOverrideTraitCollection:compactTraitCollection forChildViewController:secondaryController];
[self.navigationController pushViewController:secondaryController animated:YES];
Interestingly, this fixes the pop animation bug, but then my primary controller (self
) is still in a Regular horizontalSizeClass
... Furthermore, this seems like bad practice. My view controllers shouldn't need to know anything about their presentation! That should be handled by the UIPresentationController
, and seems supported by the fact that the presentation controller has a overrideTraitCollection
property.
Turns out the culprit was the implementation of supportedInterfaceOrientations
, relying on size classes:
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
// Don't do this if you ever override size classes
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
{
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}
Because the horizontalSizeClass
of the "half modal" controllers was overridden to use UIUserInterfaceSizeClassCompact
, they were assuming a Portrait-only orientation. The navigation controller didn't know how to handle that.
Changing the above code to rely on device type fixes the issue:
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
// Basing off of size classes causes unexpected behavior when overriding size classes - use interface idiom instead
if (self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone)
{
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}
This probably should have been the way to go in the first place, but given Apple's encouragement of being device-agnostic and only relying on size classes, it wasn't what I did.
Anyway, for prosperity, here's the test project I used to debug this: https://github.com/bradgmueller/half-modal-test