57

I gave an app with say 10 view controllers. I use navigation controller to load/unload them.

All but one are in portrait mode. Suppose the 7th VC is in landscape. I need it to be presented in landscape when it gets loaded.

Please suggest a way to force the orientation go from portrait to landscape in IOS 6 (and it will be good to work in IOS 5 as well).

Here is how I was doing it BEFORE IOS 6:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    UIViewController *c = [[[UIViewController alloc]init] autorelease];
    [self presentModalViewController:c animated:NO];
    [self dismissModalViewControllerAnimated:NO];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

Presenting and dismissing a modal VC was forcing the app to review its orientation, so shouldAutorotateToInterfaceOrientation was getting called.

What I have have tried in IOS 6:

- (BOOL)shouldAutorotate{
    return YES;
}
-(NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskLandscape;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationLandscapeLeft;
}

On load, the controller keeps staying in portrait. After rotating the device, the orientation changes just ok. But I need to make the controller to rotate automatically to landscape on load, thus the user will have to rotate the device to see the data correctly.

Another problem: after rotating the device back to portrait, the orientation goes to portrait, although I have specified in supportedInterfaceOrientations only UIInterfaceOrientationMaskLandscape. Why it happens?

Also, NONE of above 3 methods are getting called.

Some (useful) data:

  1. In my plist file I have specified 3 orientations - all but upside down.
  2. The project was started in Xcode 4.3 IOS 5. All classes including xibs were created before Xcode 4.5 IOS 6, now I use the last version.
  3. In plist file the status bar is set to visible.
  4. In xib file (the one I want to be in landscape) the status bar is "None", the orientation is set to landscape.

Any help is appreciated. Thanks.

John Smith
  • 2,012
  • 1
  • 21
  • 33
  • I have your same issue, did you solve it ? – aneuryzm Oct 01 '12 at 11:44
  • I hate the new ios 6. I have to change all my applications to make the orientation work correctly. – Bagusflyer Oct 10 '12 at 05:30
  • 5
    yop, @bagusflyer, das ist da life of da programmer ))) – John Smith Oct 10 '12 at 06:32
  • 1
    This problem like my problem and i solved. Solution here: http://stackoverflow.com/questions/14658268/why-cant-i-force-landscape-orientation-when-use-uinavigationcontroller – Bob Feb 04 '13 at 11:15
  • Your problem is works for me as a solution. I too want the same and i just wrote your ios6 code and it works for me. Its loading the view in landscape automatically for me. – Shilpa Jun 07 '13 at 12:58
  • This is my solution for your problem in iOS 7: http://stackoverflow.com/questions/22491786/force-landscape-viewcontroller-in-ios-7/22491787#22491787 – Fede Cugliandolo Mar 19 '14 at 00:10

13 Answers13

43

Ok, folks, I will post my solution.

What I have:

  1. A view based application, with several view controllers. (It was navigation based, but I had to make it view based, due to orientation issues).
  2. All view controllers are portrait, except one - landscapeLeft.

Tasks:

  1. One of my view controllers must automatically rotate to landscape, no matter how the user holds the device. All other controllers must be portrait, and after leaving the landscape controller, the app must force rotate to portrait, no matter, again, how the user holds the device.
  2. This must work as on IOS 6.x as on IOS 5.x

Go!

(Update Removed the macros suggested by @Ivan Vučica)

In all your PORTRAIT view controllers override autorotation methods like this:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
    return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
}
-(BOOL)shouldAutorotate {
    return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

You can see the 2 approaches: one for IOS 5 and another For IOS 6.

The same for your LANDSCAPE view controller, with some additions and changes:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
    [image_signature setImage:[self resizeImage:image_signature.image]];
    return (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
-(BOOL)shouldAutorotate {
    return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
    [image_signature setImage:[self resizeImage:image_signature.image]];
    return UIInterfaceOrientationMaskLandscapeLeft;
}

ATTENTION: to force autorotation in IOS 5 you should add this:

- (void)viewDidLoad{
    [super viewDidLoad];
    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0)
        [[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationLandscapeLeft animated:NO];    
}

Analogically, after you leave the LANDSCAPE controller, whatever controller you load, you should force again autorotation for IOS 5, but now you will use UIDeviceOrientationPortrait, as you go to a PORTRAIT controller:

- (void)viewDidLoad{
        [super viewDidLoad];
        if ([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0)
            [[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationPortrait animated:NO];    
    }

Now the last thing (and it's a bit weird) - you have to change the way you switch from a controller to another, depending on the IOS:

Make an NSObject class "Schalter" ("Switch" from German).

In Schalter.h say:

#import <Foundation/Foundation.h>

@interface Schalter : NSObject
+ (void)loadController:(UIViewController*)VControllerToLoad andRelease:(UIViewController*)VControllerToRelease;
@end

In Schalter.m say:

#import "Schalter.h"
#import "AppDelegate.h"

@implementation Schalter
+ (void)loadController:(UIViewController*)VControllerToLoad andRelease:(UIViewController*)VControllerToRelease{

    //adjust the frame of the new controller
    CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
    CGRect windowFrame = [[UIScreen mainScreen] bounds];
    CGRect firstViewFrame = CGRectMake(statusBarFrame.origin.x, statusBarFrame.size.height, windowFrame.size.width, windowFrame.size.height - statusBarFrame.size.height);
    VControllerToLoad.view.frame = firstViewFrame;

    //check version and go
    if (IOS_OLDER_THAN_6)
        [((AppDelegate*)[UIApplication sharedApplication].delegate).window addSubview:VControllerToLoad.view];
    else
        [((AppDelegate*)[UIApplication sharedApplication].delegate).window setRootViewController:VControllerToLoad];

    //kill the previous view controller
    [VControllerToRelease.view removeFromSuperview];
}
@end

NOW, this is the way you use Schalter ( suppose you go from Warehouse controller to Products controller ) :

#import "Warehouse.h"
#import "Products.h"

@implementation Warehouse
Products *instance_to_products;

- (void)goToProducts{
    instance_to_products = [[Products alloc] init];
    [Schalter loadController:instance_to_products andRelease:self];
}

bla-bla-bla your methods

@end

Of course you must release instance_to_products object:

- (void)dealloc{
     [instance_to_products release];
     [super dealloc];
}

Well, this is it. Don't hesitate to downvote, I don't care. This is for the ones who are looking for solutions, not for reputation. Cheers! Sava Mazare.

John Smith
  • 2,012
  • 1
  • 21
  • 33
  • 7
    Those `#ifdef IOS_OLDER_THAN_6` lines don't do anything, since you obviously defined the macros unconditionally. And even if they did, they'd do compile time checking. Your later use of these macros is alright. – Ivan Vučica Oct 09 '12 at 13:41
  • of course they dont do anything, they shouldnt. they are only declared, then, in compile time, their values are set as regular code – John Smith Oct 09 '12 at 14:00
  • Sava, I understand why you `#define` them and why you use them later on, that's perfectly normal :-) But I don't understand why you check if they were defined using `#ifdef`. Your second and third block of code need no `#ifdef` checks :-) – Ivan Vučica Oct 11 '12 at 10:50
  • @IvanVučica, maybe you were right, but ... well, I think I have tested it without #ifdef and it didn't work, but I am not sure. Well, it works perfectly WITH that #ifdef anyway ))). Thanks for the notice, I will check it – John Smith Oct 12 '12 at 07:47
  • 3
    These macros (e.g. `IOS_OLDER_THAN_6`) should not be used for checking the iOS version. `systemVersion` is not a float value (e.g. 5.1.1 is not a float), and it actually can sometimes lead to the incorrect result. [See here for the correct way of doing this](http://stackoverflow.com/a/5337804/119114) – Nate Nov 11 '12 at 09:34
  • 1
    [[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationPortrait animated:NO]; I also tried this for iOS 6 and it worked. I didn't have to do your wired thing :) What do you think? please advice. by the way I had the exact same situation you explained in details in your answer bellow. – hasan Apr 09 '13 at 23:38
  • Actually, hasan's status bar orientation trick worked for me :) – AWrightIV Sep 28 '13 at 03:01
  • a better solution: http://stackoverflow.com/questions/22491786/force-landscape-viewcontroller-in-ios-7/22491787#22491787 – Fede Cugliandolo Mar 19 '14 at 00:11
34

This should work, it's similar to the pre-iOS 6 version, but with a UINavigationController:

UIViewController *portraitViewController = [[UIViewController alloc] init];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController:portraitViewController];
[self.navigationController presentModalViewController:nc animated:NO];
[self.navigationController dismissModalViewControllerAnimated:NO];

I'm calling this before I'm pushing the next UIViewController. It will force the next pushed UIViewController to be displayed in Portrait mode even if the current UIViewController is in Landscape (should work for Portrait to Landscape too). Works on iOS 4+5+6 for me.

sam-w
  • 7,478
  • 1
  • 47
  • 77
Chunkylover53
  • 365
  • 2
  • 2
  • Not really, @Chunkylover53, the accepted answer was the closest to the solution I have found. I will post it soon. – John Smith Oct 02 '12 at 13:35
  • Dude, you're awesome ))) I have tried your hint long after posting my solution and it worked. I didn't understand you right. I have turned back to navigation-basse. I'm gonna post the solution soon – John Smith Oct 12 '12 at 13:15
  • 2
    Can you elaborate more on when this should be used? I'm running into the same issue where I need the next pushed view controller to be in landscape when the rest of my view controllers are portrait. I am using a UINavigationController, but not sure where this code should be placed. I tried having this code before I pushed my landscape view controller but that didn't work – Ken Woo Dec 01 '12 at 23:57
  • This workaround also works for non-navigationController app. I've simply used my rootViewController to present modal UIViewController and the main profit was that "preferredInterfaceOrientationForPresentation" was called. And in this method I've used "UIDeviceOrientation" to detect what orientation device has. Works like a charm! :) – Dmitry Zhukov Jan 23 '13 at 18:10
  • But i got some problem over when i dismissModalViewController then my all UI is disturbed.this problem is only in iOS 6 – Deep Arora Jul 09 '13 at 22:05
  • am i missing how/where this is telling the presented view controller to be landscape or portrait ? is the idea that since it is a root view controller the orientation methods will be called ? – jesses.co.tt Sep 02 '13 at 18:55
  • I'm a newbie....where should this code go? I put it before performing a segue but the destination view still rotates to portrait? Thanks. – maddog Jan 20 '14 at 01:53
  • changing the segue style to modal made this work for me. – maddog Jan 26 '14 at 01:34
14

I think that best solution is to stick to official apple documentation. So according to that I use following methods and everything is working very well on iOS 5 and 6. In my VC I override following methods:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return UIInterfaceOrientationIsPortrait(interfaceOrientation);
}

Methods for iOS 6, first method returns supported orientation mask (as their name indicate)

-(NSUInteger)supportedInterfaceOrientations{

    return UIInterfaceOrientationMaskPortrait;
}

second one thats tells your VC which is preferred interface orientation when VC is going to be displayed.

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return UIInterfaceOrientationPortrait;
}

Just change Portrait for orientation that you want ;) This solution is working smooth, I don't like the idea of creating macros and other stuff, that goes around this simple solution. Hope this help...

jengelsma
  • 8,192
  • 4
  • 21
  • 21
Skodik.o
  • 506
  • 4
  • 21
7

I had the same problem, 27 views in my application from which 26 in portrait and only one in all orientations ( an image viewer :) ). Adding the macro on every class and replace the navigation wasn't a solution I was comfortable with...

So, i wanted to keep the UINavigationController mechanics in my app and not replace this with other code.

What to do:

@1 In the application delegate in method didFinishLaunchingWithOptions

if ([[UIDevice currentDevice].systemVersion floatValue] < 6.0)
{
    // how the view was configured before IOS6
    [self.window addSubview: navigationController.view];
    [self.window makeKeyAndVisible];
}
else
{
    // this is the code that will start the interface to rotate once again
    [self.window setRootViewController: self.navigationController];
}

@2 Because the navigationController will just responde with YES for autorotation we need to add some limitations: Extend the UINavicationController -> YourNavigationController and link it in the Interface Builder.

@3 Override the "anoying new methods" from navigation controller.

Since this class is custom only for this application it can take responsibility for it's controllers and respond in their place.

-(BOOL)shouldAutorotate {

    if ([self.viewControllers firstObject] == YourObject)
    {
         return YES;
    }
    return NO;
}
- (NSUInteger)supportedInterfaceOrientations {
     if ([self.viewControllers firstObject] == YourObject)
    {
         return UIINterfaceOrientationMaskLandscape;
    }
    return UIInterfaceOrientationMaskPortrait;
}

I hope this will help you,

Vlad Cioaba
  • 298
  • 2
  • 8
  • Thank you, setRootViewController fixed the problems I was having! – Meta-Knight Dec 20 '12 at 17:03
  • I'm a little confused by: [self.viewControllers firstObject] since NSArray has no method called 'firstObject', anyway not sure you'd want to be looking at the first object in the list of viewcontrollers. Maybe you're meaning self.topViewController? – RefuX May 16 '13 at 19:48
  • UIViewController* vc = (UIViewController*)[self.viewControllers lastObject]; if ([NSStringFromClass([vc class]) isEqualToString:@"YourControllerClassName"]) { return UIInterfaceOrientationMaskAll; } return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; – Vlad Cioaba Dec 20 '14 at 22:56
3

From the iOS 6 Release Notes:

Now, iOS containers (such as UINavigationController) do not consult their children to determine whether they should autorotate.

Does your rootViewController pass the shouldAutoRotate message down the ViewController hierarchy to your VC?

sam-w
  • 7,478
  • 1
  • 47
  • 77
  • mmm... what do you mean by passing the shouldAutoRotate? In applicationDidFinishLaunching I said [self.window setRootViewController:navigationController] (navigationController is an instance to UINavigationController in AppDelegate) – John Smith Sep 28 '12 at 14:00
  • As stated in the release notes above, only the `rootViewController` is asked which orientations are valid. If your landscape-only viewController is not the `rootViewController` then it won't get asked - the `UINavigationController` will just return whichever orientations it thinks are valid. – sam-w Sep 29 '12 at 14:51
  • But how can I make it to be root if it shouldn't be - it is the (let's say) 7th in navigation controller stack. – John Smith Sep 29 '12 at 20:20
  • Thanks! I did it. A bit different, but your advice lead to the right approach – John Smith Oct 01 '12 at 15:51
  • Good stuff :) it might be worth posting your final solution as an answer to this question if it contains any information that might help out others in the future – sam-w Oct 02 '12 at 13:29
  • from my experience, answering your own question leads to downvoting. But I will post some code later, when I am free, for I am at work now. – John Smith Oct 02 '12 at 15:33
  • Not at all: self-answering [is completely valid and is encouraged](http://meta.stackexchange.com/questions/17463/can-i-answer-my-own-questions-even-those-where-i-knew-the-answer-before-asking). Though I'd make sure that any answer you post is general enough to be useful to people coming across the problem in future: i.e. don't post swathes of code only applicable to your project :) – sam-w Oct 03 '12 at 08:16
  • I am having the same issue, is there any examples how you managed to get this working? – Robert Oct 04 '12 at 03:21
  • 1
    see the solution, @sjwarner, I have posted as "answer to own question" – John Smith Oct 04 '12 at 11:01
2

I used the same method as OP pre-ios6 (present and dismiss a modal VC) to show a single view controller in landscape mode (all others in portrait). It broke in ios6 with the landscape VC showing in portrait.

To fix it, I just added the preferredInterfaceOrientationForPresentation method in the landscape VC. Seems to work fine for os 5 and os 6 now.

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}

- (BOOL)shouldAutorotate
{
return NO;
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{    
return UIInterfaceOrientationLandscapeLeft;
}
2

Hey guys after tryng a lot of different possible solutions with no success i came out with the following solution hope it helps!.

I prepared a recipe :).

Problem: you need change orientation of viewcontrollers using navigationcontroller in ios 6.

Solution:

navigation suggested

step 1. one initial UIviewcontroler to trigger modal segues to landscape and portrait UInavigationControllers as picture shows....

more deeply in UIViewController1 we need 2 segues actions according to global variable at Appdelegate....

-(void)viewDidAppear:(BOOL)animated{
    if([globalDelegate changeOrientation]==0){
        [self performSegueWithIdentifier:@"p" sender:self];
    }
    else{
        [self performSegueWithIdentifier:@"l" sender:self];
    }

}

also we need a way back to portrait &| landscape....

- (IBAction)dimis:(id)sender {
    [globalDelegate setChangeOrientation:0];
    [self dismissViewControllerAnimated:NO completion:nil];
}

step 2. the first Pushed UiViewControllers at each NavigationController goes with...

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

-(BOOL)shouldAutorotate{
    return YES;
}

step 3. We overwrite supportedInterfaceOrientations method at subclass of UInavigationController....

in your customNavigationController we have .....

-(NSUInteger)supportedInterfaceOrientations{
    if([self.visibleViewController isKindOfClass:[ViewController2 class]]){

        return UIInterfaceOrientationMaskPortrait;
    }
    else{
        return UIInterfaceOrientationMaskLandscape;

    }

}

step 4. At storyboard or by code, set wantsFullScreenLayout flag to yes, to both portrait and landscape uinavigationcontrollers.

47tucanae
  • 133
  • 2
  • 8
1

Try segueing to a UINavigationController which uses a category or is subclassed to specify the desired orientation, then segue to the desired VC. Read more here.

Community
  • 1
  • 1
Michael Mangold
  • 1,741
  • 3
  • 18
  • 32
1

As an alternative you can do the same using blocks:

UIViewController *viewController    = [[UIViewController alloc] init];
viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:viewController animated:NO completion:^{
    [self dismissViewControllerAnimated:NO completion:nil];
}];

Also, call it before pushing the new view.

JP Illanes
  • 3,665
  • 39
  • 56
0

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

0

I had the same problem. If you want to force a particular view controller to appear in landscape, do it right before you push it into the navigation stack.

UIInterfaceOrientation currentOrientation = [[UIApplication sharedApplication] statusBarOrientation];
        if (currentOrientation == UIInterfaceOrientationPortrait ||
            currentOrientation == UIInterfaceOrientationPortraitUpsideDown)
            [[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeLeft];

        UIViewController *vc = [[UIViewController alloc] init];
        [self.navigationController pushViewController:vc animated:YES];
        [vc release];
  • 1
    ` [[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeLeft];` doesn't work for iOS 6 – dev Feb 23 '14 at 21:17
0

I solved it by subclassing UINavigationController and overriding the supportedInterfaceOrientations of the navigation Controller as follow:

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

All the controllers implemented supportedInterfaceOrientations with their desired orientations.

Zeev Vax
  • 914
  • 7
  • 13
-1

I have used the following solution. In the one view controller that has a different orientation than all the others, I added an orientation check in the prepareForSegue method. If the destination view controller needs a different interface orientation than the current one displayed, then a message is sent that forces the interface to rotate during the seque.

#import <objc/message.h>

...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if(UIDeviceOrientationIsLandscape(self.interfaceOrientation))
    {
        UIInterfaceOrientation destinationOrientation;

        if ([[segue destinationViewController] isKindOfClass:[UINavigationController class]])
        {
            UINavigationController *navController = (UINavigationController *)[segue destinationViewController];
            destinationOrientation = [navController.topViewController preferredInterfaceOrientationForPresentation];
        } else
        {
            destinationOrientation = [[segue destinationViewController] preferredInterfaceOrientationForPresentation];
        }

        if ( destinationOrientation == UIInterfaceOrientationPortrait )
        {
            if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)])
            {
                objc_msgSend([UIDevice currentDevice], @selector(setOrientation:), UIInterfaceOrientationPortrait );
            }
        }
    }
}
Marco
  • 1
  • 1