100

I have an iPhone app that uses a UINavigationController to present a drill-down interface: First one view, then another, up to four levels deep. I want the first three views restricted to portrait orientation and only the last view should be allowed to rotate to landscape. When returning from the fourth view to the third and the fourth view was in landscape orientation I want everything to rotate back to portrait.

In iOS 5 I simply defined shouldAutorotateToInterfaceOrientation: in each of my view controllers to return YES for the allowable orientations. Everything worked as described above, including the return to portrait even if the device was being held in landscape orientation when returning from view controller #4 to #3.

In iOS 6 all view controllers rotate to landscape, breaking those that weren't meant to. The iOS 6 release notes say

More responsibility is moving to the app and the app delegate. Now, iOS containers (such as UINavigationController) do not consult their children to determine whether they should autorotate. [...] The system asks the top-most full-screen view controller (typically the root view controller) for its supported interface orientations whenever the device rotates or whenever a view controller is presented with the full-screen modal presentation style. Moreover, the supported orientations are retrieved only if this view controller returns YES from its shouldAutorotate method. [...] The system determines whether an orientation is supported by intersecting the value returned by the app’s supportedInterfaceOrientationsForWindow: method with the value returned by the supportedInterfaceOrientations method of the top-most full-screen controller.

So I subclassed UINavigationController, gave my MainNavigationController a boolean property landscapeOK and used this to return the allowable orientations in supportedInterfaceOrientations. Then in each of my view controllers' viewWillAppear: methods I have a line like this

    [(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

to tell my MainNavigationController the desired behavior.

Here comes the question: If I now navigate to my fourth view in portrait mode and turn the phone over it rotates to landscape. Now I press the back button to return to my third view which is supposed to work portrait only. But it doesn't rotate back. How do I make it do that?

I tried

    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]

in the viewWillAppear method of my third view controller, but it doesn't do anything. Is this the wrong method to call or maybe the wrong place to call it or should I be implementing the whole thing in a totally different way?

TheNeil
  • 3,321
  • 2
  • 27
  • 52
Harald Bögeholz
  • 1,163
  • 2
  • 8
  • 8

15 Answers15

96

I had the same problem and found a solution that works for me. To make it work, it is not sufficient to implement - (NSUInteger)supportedInterfaceOrientations in your UINavigationController. You also need to implement this method in your controller #3, which is the first one to be portrait-only after popping controller #4. So, I have the following code in my UINavigationController:

- (BOOL)shouldAutorotate
{
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    if (self.isLandscapeOK) {
        // for iPhone, you could also return UIInterfaceOrientationMaskAllButUpsideDown
        return UIInterfaceOrientationMaskAll;
    }
    return UIInterfaceOrientationMaskPortrait;
}

In view controller #3, add the following:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

You don't need to add anything to your view controllers #1, #2, and #4. This works for me, I hope it will help you.

Flo
  • 2,309
  • 20
  • 27
  • 8
    you solved something that people was trying to solve for months! you deserve a lot of credit for this! I added supportedInterfaceOrientations to a category on UIViewController and it works perfectly as a default for those portrait-only controllers. – dwery Oct 26 '12 at 19:03
  • 3
    Among 100 different answers on SO, this is the one that really works. Thanks :-) – Christer Nordvik Nov 15 '12 at 09:57
  • @dwery is this iOS 6 only? – dgund Dec 17 '12 at 23:06
  • Yes, thank you Flo. The point of making sure that view controller #3 also implements supportedInterfaceOrientations is exactly what I needed also. – Jason Jan 14 '13 at 12:29
  • 16
    I have this set up and still getting wrong orientation when going BACK from portrait to a view controller restricted to only landscape. It doesn't autorotate to the landscape - Anyone else still seeing it? – Oded Ben Dov Feb 19 '13 at 16:34
  • Ben, I have seen it, the same for me – Tom May 09 '13 at 14:02
  • 1
    what if the desired landscape view controller is being presented from a segue and there is no navigation controller ? – jesses.co.tt Sep 02 '13 at 19:04
  • @jesses then you need to set this answer up with the root view (the View which initiates the segues) – Taskinul Haque Sep 20 '13 at 15:43
  • 1
    Edit the last one to be NSUInteger as return type. – absessive May 19 '14 at 22:19
69

Add a CustomNavigationController

Override these methods in it:

-(BOOL)shouldAutorotate
{
    return [[self.viewControllers lastObject] shouldAutorotate];
}

-(NSUInteger)supportedInterfaceOrientations
{
    return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}

Now add all orientations in the plist

enter image description here

In the view controller add only the required ones:

-(BOOL)shouldAutorotate
{
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

these methods override the navigation controller methods

Zaraki
  • 3,720
  • 33
  • 39
  • 5
    You really nailed it with this answer! Thank you so much. You really helped out a lot. This has been a big problem for me for a while. Great job. – K2Digital Dec 29 '12 at 06:26
  • Thankyou! This took me so much time and energy to figure out! – bkbeachlabs Jan 06 '13 at 17:23
  • You are life saver....This works in my app as well. Actually what i want in my app is i have all `ViewController` are in Portrait mode and my one `ViewController` is in Landscap & Portrait mode. I also give support in both iOS 5.0 & iOS 6.0 That's why i am confuse to work around but this is great solution. i add **CustomNavigationController** in my root view controller and give support according to my needs. Thanks once again. – Bhavin_m May 25 '13 at 12:17
  • what if the desired landscape view controller is being presented from a segue and there is no navigation controller ? – jesses.co.tt Sep 02 '13 at 19:05
  • The apple's UIPrintingViewController uses the nav controller's supported interface orientations and results in infinite loop and crashes. How will we figure out that? – Angad Manchanda Sep 24 '13 at 22:34
  • It rotating the whole application I have tabBarCaontroller in that different navigationControllers.Any idea ? – The iCoder Nov 19 '13 at 12:08
  • @Kenpachi its working fine with Navigation controller, but i have navigation controller wit UITabBarController its not working . please advice... – Ram S Jan 17 '15 at 08:01
  • do you have different navigation controllers for each tab? – Zaraki Jan 19 '15 at 05:23
  • This is the perfect answer – pkc456 Jul 07 '15 at 15:32
  • got an error in `[self.viewControllers lastObject]` – Monika Patel Sep 09 '15 at 05:01
  • and what if i need not present viewcontroller, but push it from UINVC? – Evgeny Fedin May 05 '16 at 11:16
  • this will work when you push it and not present. You should use the CustomNVC as described above. – Zaraki May 05 '16 at 12:38
9

After looking through every answer in countless similar questions on SO, none of the answers worked for me, but they did give me some ideas. Here's how I ended up solving the problem:

First, make sure your Supported Interface Orientations in your project's target contain all orientations that you want for your rotating view.

enter image description here

Next, make a category of UINavigationController (since Apple says not to subclass it):

@implementation UINavigationController (iOS6AutorotationFix)

-(BOOL)shouldAutorotate {
    return [self.topViewController shouldAutorotate];
}

@end

Import that category and the view controller that you want to be able to rotate (which I'll call RotatingViewController) to your highest level view controller, which should contain your navigation controller. In that view controller, implement shouldAutorotate as follows. Note that this should not be the same view controller that you want to rotate.

-(BOOL)shouldAutorotate {

    BOOL shouldRotate = NO;

    if ([navigationController.topViewController isMemberOfClass:[RotatingViewController class]] ) {
        shouldRotate = [navigationController.topViewController shouldAutorotate];
    }

    return shouldRotate;
}

Finally, in your RotatingViewController, implement shouldAutorotate and supportedInterfaceOrientations as follows:

-(BOOL)shouldAutorotate {
    // Preparations to rotate view go here
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAllButUpsideDown; // or however you want to rotate
}

The reason you need to do this is because iOS 6 gives control of rotation to the root view controller instead of the top view controlller. If you want an individual view's rotation to behave differently than other views in the stack, you need to write a specific case for it in the root view controller.

Brian
  • 14,610
  • 7
  • 35
  • 43
  • 2
    This is an excellent answer that deserves more love. Here's the piece of information it contains that I couldn't find anywhere else, and which is critical: **If you want an individual view's rotation to behave differently than other views in the stack, you need to write a specific case for it in the root view controller** – shmim Nov 10 '14 at 20:00
  • I suppose this will work if you have a navigationController as the first member of your app's storyboard as initial controller but what about the case when the initial controller is simply a ViewController? – Duck Mar 13 '17 at 18:45
4

I don't have enough reputation to comment on @Brian's answer so I'll add my note here.

Brian mentioned that iOS6 gives the rotation control to the rootViewController - this could not only be a UINavigationController as mentioned but also a UITabBarController, which it was for me. My structure looks like this:

  • UITabBarController
    • UINavigationController
      • UIViewControllers ...
    • UINavigationController
      • UIViewControllers ...

So I added the methods first in a custom UITabBarController, then in a custom UINavigationController and then lastly in the specific UIViewController.

Example from the UITabBarController and UINavigationController:

- (BOOL)shouldAutorotate {
    return [self.viewControllers.lastObject shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations {
    return [self.viewControllers.lastObject supportedInterfaceOrientations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return [self.viewControllers.lastObject shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.viewControllers.lastObject preferredInterfaceOrientationForPresentation];
}
Brian
  • 14,610
  • 7
  • 35
  • 43
micmdk
  • 951
  • 1
  • 8
  • 12
  • I actually did use a UITabBarController as an instance variable in my root view controller because you're not supposed to subclass UITabBarController in iOS versions before 6.0 and I needed to support back to iOS 5.0. – Brian Aug 20 '13 at 19:33
  • @micmdk I have same problem. please advise how to resolved, I have copied your code and put into custom Navigation Controller and customUabbarController and set other view controller as Brian's Guided. but still not getting fixed view controller in portrait. – Ram S Jan 17 '15 at 08:16
  • @Brian I have same problem. please advise how to resolved, I have copied your code and put into custom Navigation Controller and customUabbarController and set other view controller as Micmdk's Guided. but still not getting fixed view controller in portrait in ios8. – Ram S Jan 17 '15 at 08:16
3

I'd like to give a partial answer to my own question. I found the following line of code, used in the viewWillAppear method of my third UIViewController, to work:

[[UIDevice currentDevice] 
      performSelector:NSSelectorFromString(@"setOrientation:") 
           withObject:(id)UIInterfaceOrientationPortrait];

But I don't really like this solution. It is using a trick to assign to a read-only property which according to Apple documentation represents the physical orientation of the device. It's like telling the iPhone to jump to the correct orientation in the hand of the user.

I am very tempted to leave this in my app since it simply works. But it doesn't feel right so I'd like to leave the question open for a clean solution.

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Harald Bögeholz
  • 1,163
  • 2
  • 8
  • 8
  • 4
    Your app will be most likely rejected if you leave it there. I am looking for solution to this as well, but looks like it is not possible. All I could find is answer to this question: http://stackoverflow.com/questions/7196300/ios-prevent-rotation-in-certain-views?answertab=active#tab-top it sort of works but for some reason it breaks button touch handling in one of my viewcontrollers – Lope Oct 06 '12 at 11:39
  • I have taken my chances and submitted to Apple. Still waiting for review ... I'll keep you posted. – Harald Bögeholz Oct 08 '12 at 07:11
  • Apple just approved my app with this hack in place, yey! I hope it won't backfire on me in a future iOS update. In the meantime I am still open for suggestions for a clean solution! – Harald Bögeholz Oct 12 '12 at 19:48
  • 1
    @HaraldBögeholz Grt suggetion. but when i m working in new xCode with ARC than i cant use the ans . i am getting "Cast of NSInteger (aka int ) to id is disallowed with ARC PerformSelector may cause a leak because its selector is unknown" how can i resolved this ? i am really appriciated to you if we got solution. When i off ARC for perticular View than app crash due to some leak data. Please suggesti me – Hitarth Feb 07 '13 at 14:09
  • Sorry, can't help you there. I haven't done any iOS programming since my last post ... maybe you should look at the other answers to my question. (Didn't look into them myself, I must admit, since my hack still works for me and I have other obligations.) – Harald Bögeholz Feb 07 '13 at 22:29
  • 4
    Using a private API is NEVER the solution. – Javier Soto Jul 11 '13 at 22:25
1

This is I am using for orientation support in ios 6.0

-(BOOL)shouldAutorotate{
    return YES;
}  

 - (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;
}


- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{  
  return UIInterfaceOrientationPortrait;
}
NSUserDefault
  • 1,794
  • 1
  • 17
  • 38
ASHISHT
  • 305
  • 1
  • 14
1

Being that this is a highly viewed thread. I thought I would add what I believe is the easiest answer. This works for ios8 and up

-(BOOL)shouldAutorotate
{
    return YES;
}

and

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

That's it. Enjoy!

Oh, and my ViewControllers are embedded in a navigation controller, which I did not need to subclass or configure in any way.

Reeder32
  • 89
  • 4
0

You want to Force iOS 6 app portrait only then you can add to a UIViewController subclass below methods

- (BOOL)shouldAutorotate {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        return YES;
    } else {
        return NO;
    }
}


- (NSUInteger)supportedInterfaceOrientations {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        return UIInterfaceOrientationMaskAll;
    } else {
        return UIInterfaceOrientationMaskPortrait;
    }
}
Vaibhav Saran
  • 12,848
  • 3
  • 65
  • 75
  • 2
    The problem is that unless that view is the root view (which it isn't in this question), shouldAutorotate doesn't get called. – Brian Apr 24 '13 at 15:54
  • for whatever reason return type of `NSUInteger` throws a warning despite it being the correct var type. if you have OCD use `UIInterfaceOrientationMask` instead. – Chris J Apr 12 '16 at 06:50
  • Also, this solution worked perfectly for a modal view controller (a camera view) i have without any need for subclassing UINavigationController. I'm guessing modals are treated independently. Not relevant to the original question, but useful info to have design wise. – Chris J Apr 12 '16 at 06:51
0

This might not work for everyone, but it works great for me. Instead of implementing...

[(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

in viewWillAppear in all of my controllers, I decided to centralize this process inside of my UINavigationController subclass by overriding the UINavigationControllerDelegate method navigationController:willShowViewController:animated:

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

    self.previousVCLandscapeOK = self.isLandscapeOK; // Store the current VC's orientation preference before pushing on the new VC so we can set this again from within the custom "back" method
    self.isLandscapeOK = NO; // Set NO as default for all VC's
    if ([viewController isKindOfClass:[YourViewController class]]) {
        self.isLandscapeOK = YES;
    }
}

I have found that this delegate method doesn't get called when popping a VC off of the nav stack. This wasn't an issue for me because I am handling the back functionality from within my UINavigationController subclass so that I can set the proper navigation bar buttons and actions for specific VC's like this...

if ([viewController isKindOfClass:[ShareViewController class]]) {

    UIButton* backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 57, 30)];
    [backButton setImage:[UIImage imageNamed:@"back-arrow"] forState:UIControlStateNormal];
    [backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem* backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backButtonItem;

    UIImageView* shareTitle = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"share-title"]];
    [shareTitle setContentMode:UIViewContentModeScaleAspectFit];
    [shareTitle setFrame:CGRectMake(0, 0, shareTitle.frame.size.width - 10, shareTitle.frame.size.height - 10)];
    viewController.navigationItem.titleView = shareTitle;

} else if(...) {
    ...
}

Here is what my back method looks like to handle popping the VC off of the stack and to set the appropriate rotation preference...

- (void)back {
    self.isLandscapeOK = self.previousVCLandscapeOK;
    self.previousVCLandscapeOK = NO;
    [self popViewControllerAnimated:YES];
}

So, as you can see, basically all that is happening is I'm first setting me two properties...

@property (nonatomic) BOOL isLandscapeOK;
@property (nonatomic) BOOL previousVCLandscapeOK;

in navigationController:willShowViewController:animated: which will determine what the supported orientations are within that VC that is about to be presented. When popping a VC, my custom "back" method is being called and I'm then setting the isLandscapeOK to what was stored via the previousVCLandscapeOK value.

As I said, this might not work for everyone, but it works great for me and I don't have to worry about adding code to each of my view controllers, I was able to keep it all centralized in the UINavigationController subclass.

Hope this helps someone as it did me. Thanks, Jeremy.

Jeremy Fox
  • 2,668
  • 1
  • 25
  • 26
0

Go to you Info.plist file and make the changeenter image description here

0

I wanted to have all my VCs locked to portrait orientation except one. This is what worked for me.

  1. Add support for all orientations in the plist file.
  2. In the root view controller, detect the kind of view controller thats on top of the window and set the orientation of the app accordingly in the supportedInterfaceOrientations method. For example, I needed my app to rotate only when the webview was on top of the stack. Here's what I added in my rootVC :

    -(NSUInteger)supportedInterfaceOrientations
    {
        UIViewController *topMostViewController = [[Utils getAppDelegate] appNavigationController].topViewController;
        if ([topMostViewController isKindOfClass:[SVWebViewController class]]) {
            return UIInterfaceOrientationMaskAllButUpsideDown;
        }
        return UIInterfaceOrientationMaskPortrait;
    }
    
Steph Sharp
  • 11,462
  • 5
  • 44
  • 81
Trunal Bhanse
  • 1,651
  • 1
  • 17
  • 27
  • Trunal, This is a few months old but can you give a little more detail on how you detect the kind of view controller that is on top. Specifically I am not sure what you have going here `[[Utils getAppDelegate] appNavigationController]`. Utils I'm guessing is some class you have but I'm at loss what you do in it. Any help would be great! – Ben Apr 18 '14 at 06:14
0

I don't have enough reputation to answer @Ram S question under @micmdk reply so I'll add my note here.

When you use UITabbarController, try to change self.viewControllers.lastObject in @micmdk's code to self.selectedViewController like this:

- (BOOL)shouldAutorotate {
    return [self.selectedViewController shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations {
    return [self.selectedViewController supportedInterfaceOrientations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return [self.selectedViewController shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
phantom_2
  • 543
  • 7
  • 14
0

I've solved the same kind of issue.

If you are using the UINavigationController to push the view controllers, you have to set the methods below.

extension UINavigationController{

    override open var shouldAutorotate: Bool {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return true
        }
        return false
    }

    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return .portrait
        }
        return .landscapeRight

    }
    override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return .portrait
        }
        return .landscapeRight
    }
}

In place of LoginViewController use which UIViewController you want show. In my case, I want to show the LoginViewController in the Portrait mode other ViewControllers in landscape mode.

Pang
  • 9,564
  • 146
  • 81
  • 122
Ramakrishna
  • 712
  • 8
  • 26
0

You can implement and override shouldAutorotate() and supportedInterfaceOrientations var in all of your View Controller classes that should be presented in a different orientations than ones defined in PLIST of your app.

However, in a nontrivial User Interface you might face a problem to add it in dozens of classes and you do not want to make all of them subclass of several common that supports it (MyBaseTableViewController, MyBaseNavigationController and MyBaseTabBarController).

Since you cannot override those method/var on UIViewController directly, you may do that on its subclasses that are typically base classes of yours like UITableViewController, UINavigationController and UITabBarController.

So you may implement a few extensions and still setup MyPreciousViewController to show in a different orientations than all others like this Swift 4 code snippet:

extension UITableViewController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

    if let last = self.navigationController?.childViewControllers.last,
        last != self {
            return last.supportedInterfaceOrientations
    } else {
        return [.portrait]
    }
    }
}

extension MyPreciousViewController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

    return [.portrait,.landscape]
    }
}


extension UINavigationController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        return [.portrait]
    }
}


extension UITabBarController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        return [.portrait]
    }
}
vedrano
  • 2,961
  • 1
  • 28
  • 25
-1

Please use the following method to solve this issue

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

return only the orientation you want!!!

  • 3
    I did that, as I wrote. This keeps my view controller from rotating, but it does *not* force it back to the only supported orientation when I return to it. – Harald Bögeholz Oct 12 '12 at 19:45
  • This doesn't work for me. Even having included this in my view controller, the view still rotates. What gives? – shim Oct 26 '12 at 05:44
  • why downvote for maxfiresolutions? It worked for me and same method given by @Flo is having many upvotes. – ViruMax May 21 '14 at 05:25