33

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?

Mihai Fratu
  • 7,579
  • 2
  • 37
  • 63
  • I were have similar problem and had patch to,, see http://stackoverflow.com/questions/25769068/supporting-multiple-interface-but-have-single-interface-in-home-screen-not-worki – Jageen Sep 19 '14 at 05:19

9 Answers9

20
- (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

ZaEeM ZaFaR
  • 1,508
  • 17
  • 22
  • It's been a while since you've answered but today I hit this problem again. And your solution works! Thanks! – Mihai Fratu Aug 12 '15 at 09:58
  • This doesn't work for me, and there is no `isPresented` property on `UIViewController` – Ric Santos Oct 08 '16 at 02:08
  • @RicSantos Please see the attached link in above answer for complete details. There is a property added in the view controller. – ZaEeM ZaFaR Oct 08 '16 at 04:35
  • this is not working for a UI page view controller. I have 2 view controllers (A, B) in my page view controller. All the breakpoints hit correctly with this code, but I still end up with landscape orientation when I move from B (landscape) to A(portrait). – nr5 Jul 23 '17 at 05:22
  • 1
    Link is dead at the moment (500). Please include relevant details in your answer. Luckily, archive has it for now: https://web.archive.org/web/20170628051957/http://swiftiostutorials.com/ios-orientations-landscape-orientation-one-view-controller/ – Fls'Zen Aug 06 '17 at 21:48
  • @Fls'Zen Thanks for providing the archived page. Here is the active link of Git source of above implementation Adding it to answer as well. – ZaEeM ZaFaR Aug 25 '17 at 09:25
7

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

jules
  • 840
  • 6
  • 14
  • i were have similar problem and solve same way like you do but got another problem explain here, http://stackoverflow.com/questions/25769068/delegate-method-supportedinterfaceorientationsforwindow-not-called-ios8gm-xcod – Jageen Sep 11 '14 at 04:49
  • After spending most of the day on this topic I found the solution in your answer. It makes sense to me and it works flawlessy in my Controllers. Thanx so much. – Fabrizio Oct 28 '14 at 15:26
5

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!

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Just to give an example, I've discovered that the Store Kit alerts now work find in landscape. The fact that they didn't work in landscape was a reason why you needed to force the app into portrait before using Store Kit. Now that reason is gone. This suggests that Apple has been going through the frameworks and removing reasons to force rotation — which, in turn, suggests that it can no longer be done. – matt Jun 29 '14 at 17:28
  • Hm... This seems weird though. I mean that means that my app MUST be able to show ALL screens in both landscape and portrait? 'Cause in my example above my app is portrait only BUT one view that is presented modally which is a photo gallery. Using the preferred orientation on the parent VC doesn't seem to work correctly in my point of view since the child VC that is presented modally rotates my parent while it is hidden. – Mihai Fratu Jun 30 '14 at 12:14
  • 1
    How exactly do you "force rotation"? I'm still having issues after dismissing a modal view controller that supports both landscape and portrait to a presenting view controller that just supports portrait – johosher Aug 22 '14 at 22:16
  • @johosher I do still see some edge cases, so be sure to package up any reproducible cases as a bug report and report it! It's going to be bad if iOS 8 gets finalized without their dealing with this. – matt Aug 22 '14 at 22:30
  • 2
    @matt Have u figured out a way to force rotate a view controller in iOS 8? – Saikiran Komirishetty Sep 11 '14 at 08:41
3

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]
}];
Airsource Ltd
  • 32,379
  • 13
  • 71
  • 75
3

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;
    }
}
xfyre
  • 93
  • 2
  • 6
3

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(){}
kuhr
  • 582
  • 8
  • 18
  • 1
    This works amazingly well. The only "gotcha" for Swift 4 users is they need to include *@objc for the canRotate()* function, i.e.: *@objc func canRotate(){}* – Chewie The Chorkie Apr 25 '18 at 21:02
1

In the root view controller, try adding:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

Worked for me.

Joshua C. Lerner
  • 1,910
  • 17
  • 12
0

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 }
Hiren Panchal
  • 2,963
  • 1
  • 25
  • 21
  • I tried you method, unfortunately the method application:supportedInterfaceOrientationsForWindow: is not being called at all when the presented view controller gets dismissed. – Jaime S Dec 14 '18 at 18:34
-1

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

Community
  • 1
  • 1
oren
  • 3,159
  • 18
  • 33