32
votes

I have this app I am working on and I need ALL my view controllers but one to be in portrait. The single one view controller that is special I need it to be able to rotate to whatever orientation the phone is in.

To do that I present it modally (not embedded in a NavigationController)

So (for example) my structure is like this:

  • window - Portrait
    • root view controller (UINavigationController - Portrait)
      • home view controller (UIViewController - Portrait)
        • details view controller (UIViewController - Portrait)
        • .
        • .
        • .
        • modal view controller (UIVIewController - All)

Now when ever I dismiss my modal view controller in a landscape position my parent view controller is ALSO rotated even though it doesn't support that orientation.

All UIViewControllers and UINavigaionControllers in the app inherit from the same general classes which have these methods implemented:

override func supportedInterfaceOrientations() -> Int
{
    return Int(UIInterfaceOrientationMask.Portrait.toRaw())
}

My modal view controller overrides this method once again and it looks like this:

override func supportedInterfaceOrientations() -> Int
{
    return Int(UIInterfaceOrientationMask.All.toRaw())
}

Update 1

It looks like this is happening only on iOS8 Beta. Does someone know if there is something that changed regarding view controller's rotation or is this just a bug in the beta?

9
I were have similar problem and had patch to,, see stackoverflow.com/questions/25769068/…Jageen

9 Answers

19
votes
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if ([self.window.rootViewController.presentedViewController isKindOfClass: [SecondViewController class]])
{
    SecondViewController *secondController = (SecondViewController *) self.window.rootViewController.presentedViewController;

    if (secondController.isPresented)
        return UIInterfaceOrientationMaskAll;
    else return UIInterfaceOrientationMaskPortrait;
}
else return UIInterfaceOrientationMaskPortrait;
}

And for Swift

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow) -> Int {

    if self.window?.rootViewController?.presentedViewController? is SecondViewController {

        let secondController = self.window!.rootViewController.presentedViewController as SecondViewController

        if secondController.isPresented {
            return Int(UIInterfaceOrientationMask.All.toRaw());
        } else {
            return Int(UIInterfaceOrientationMask.Portrait.toRaw());
        }
    } else {
        return Int(UIInterfaceOrientationMask.Portrait.toRaw());
    }

}

For more details check this link

7
votes

I'm having the same issue with an app and after days of experimentation I came up with a solution which is not very nice but it works for now. I'm using the delegate method application:supportedInterfaceOrientationsForWindow: within the appdelegate.

I created a test project and put it here on github (including a GIF which shows the result...)

// note: it's not in swift but I hope it helps anyways

5
votes

After much experimentation, I am convinced that this is a "feature" of iOS 8.

If you think about it, this makes perfect sense, because it has been coming for a long time.

  • In, say iOS 4, it was possible to force app rotation when changing view controllers in a tab bar controller and a navigation controller, as well as when presenting/dismissing a controller.

  • Then in iOS 6 it became impossible to force app rotation except when presenting/dismissing a view controller (as I explained in many answers, such as this one).

  • Now, in iOS 8, I conjecture that it will be impossible to force app rotation at all (except at launch). It can prefer a certain orientation, so that once it is in that orientation it will stay there, but it cannot force the app to go into that orientation.

    Instead, your view controller is expected to "adapt". There are several WWDC 2014 videos concentrating on "adaptation", and now I'm starting to understand that this is one reason why this is so important.

    EDIT: In seed 4, it looks like this feature (forcing rotation on presentation and dismissal) is returning!

3
votes

We have an app deployed that has a landscape controller which presents a portrait-only view controller. Was reviewed by Apple on iOS 8. We are only overriding supportedInterfaceOrientations.

It's worth noting that we found lots of differences between betas 3,4,and 5. In the end we had to wait for the GM before making a concerted effort to update our app for iOS 8.

You need to be very careful in iOS 8 to make sure that you don't do this:

[self dismissViewControllerAnimated:YES completion:nil]
[self presentViewController:vc animated:YES completion:nil]

If the outgoing vc is portrait, and the incoming one is landscape, some view frames can end up very messed up. Present the incoming vc in the completion block of the dismiss call instead.

[self dismissViewControllerAnimated:YES completion:^{
    [self presentViewController:vc animated:YES completion:nil]
}];
3
votes

This is actually easier and can be done without any additional properties (here's an example with AVPlayerViewController):

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        if ([self.window.rootViewController.presentedViewController isKindOfClass: [AVPlayerViewController class]])
            return self.window.rootViewController.presentedViewController.isBeingDismissed ? 
            UIInterfaceOrientationMaskPortrait : UIInterfaceOrientationMaskAll;
        else
            return UIInterfaceOrientationMaskPortrait;
    } else {
        return UIInterfaceOrientationMaskAll;
    }
}
2
votes

Awesome question and awesome answer provided by @ZaEeM ZaFaR! Combining his answer with this led me to a great and more generic solution.

The drawback of the first answer is that you have to manage the variable isPresented in every view controller that allows rotations. Furthermore, you have to expand the check and cast in supportedInterfaceOrientationsForWindow for every vc that allows rotation.

The drawback of the second answer is that it doesn't work; it also rotates the presenting vc when dismissing the presented vc.

This solution allows rotation in all vc where you put canRotate(){} and doesn't rotate the presenting vc.

Swift 3:
In AppDelegate.swift:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
        if (rootViewController.responds(to: Selector(("canRotate")))) {
            // Unlock landscape view orientations for this view controller if it is not currently being dismissed
            if !rootViewController.isBeingDismissed{
                return .allButUpsideDown
            }
        }
    }
    
    // Only allow portrait (standard behaviour)
    return .portrait
}

private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
    if (rootViewController == nil) {
        return nil
    }
    if (rootViewController.isKind(of: UITabBarController.self)) {
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
    } else if (rootViewController.isKind(of: UINavigationController.self)) {
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
    } else if (rootViewController.presentedViewController != nil) {
        return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
    }
    return rootViewController
}

In each view controller where rotation should be allowed:

func canRotate(){}
1
votes

In the root view controller, try adding:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

Worked for me.

0
votes

Swift 3.0 OR Above, Just check "isBeingDismissed" property of presented view controller. Below is sample code, This is will rotate presenting view controller to portrait mode immediately after presented view controller is dismissed.

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController)
{
  if rootViewController.canRotateVC == true
  {
    if baseVC.isBeingDismissed == false
    {
      return .allButUpsideDown
    }
  }
}

  return .portrait}

you can get topController by below code:

  private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController?{
if (rootViewController == nil) { return nil }if (rootViewController.isKind(of: (UITabBarController).self))
{
  return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
}
else if (rootViewController.isKind(of:(UINavigationController).self))
{
  return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
}
else if (rootViewController.presentedViewController != nil)
{
  return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController }
-1
votes

I had the same issue, finally found a solution to open the modal view controller in another UIWindow, and it worked smoothly.

Stack - iOS8 - prevent rotation on presenting viewController

For code: https://github.com/OrenRosen/ModalInWindow