2
votes

How my code is set up:

I use a navigation controller, with a delegate that controls the orientation:

class NavDelegate: UINavigationControllerDelegate {
  func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
    print("Checking orientation")
    if topViewController != nil {
      return topViewController!.supportedInterfaceOrientations
    }
    return .portrait
  }

  ...
}

The main view controller of my app is named MainController, and it's portrait-only:

class MainController: UIViewController {
  override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
  }

  ...
}

I have another controller PhotoController, which supports all four orientations:

class PhotoController: UIViewController {
  override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .all
  }

  ...
}

I push the PhotoController on top of the MainController, using pushViewController().

The problem:

Each controller by itself handles orientations correctly — MainController stays in portrait as I rotate the phone to all four orientations, and PhotoController rotates to all four orientations.

But this scenario doesn't work correctly:

  1. Put the phone in landscape, with main controller on screen.
  2. The controller is in portait (as expected).
  3. Push the PhotoController on the stack.

I expect the PhotoController to open in landscape, based on the device orientation, but it remains in portrait. If I manually rotate the phone back and forth, the controller rotates with the phone. Obviously, the user should never have to do this, rotate the phone back and forth to get the controller to rotate.

When I tap the button in MainController that pushes PhotoController, navigationControllerSupportedInterfaceOrientations() isn't invoked.

What else I tried:

  • Overriding shouldAutorotate to return true in PhotoController.
  • ... and in MainController.
  • Overriding supportedInterfaceOrientations in UINavigationController instead of using the delegate.
  • Checking UINavigationController.visibleViewController instead of .topViewController. This didn't work because the topviewController turns out to be an iOS private class UISnapshotModalViewController, not my class, so calling supportedInterfaceOrientation() on it presumably doesn't return the right value.

None of these work.

1

1 Answers

5
votes

The solution turned out to be to invoke UIViewController. attemptRotationToDeviceOrientation() in viewWillAppear() or viewDidAppear().

In the former case, the animation of pushing the controller occurs concurrently with the device rotation animation, which looks odd. In the latter case, the rotation happens after the controller transition animation completes. Which also looks odd, though in a different way.