36
votes

I´m having this issue with iOS 6 SDK: I´m having some views that should be allowed to rotate (e.g. a videoview), and some that don´t. Now I understand I have to check all orientations in the app´s Info.plist and then sort out in each ViewController, what should happen. But it doesn´t work! The app always rotates to the orientations, that are given in the Info.plist.

Info.plist:

<key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>

any ViewController that shouldn´t be allowed to rotate:

//deprecated
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

Observation: App rotates to landscape and portrait orientation. Any ideas why or what I´m doing wrong?

Cheers, Marc

Edit: My latest findings also indicate, that if you want to have rotation at some point in your app, you have to activate all four rotation directions in your project settings or Info.plist. An alternative to this is to override

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window

in your AppDelegate, which overrides the Info.plist. It isn´t possible anymore to set only Portrait in your Info.plist and then having rotation in some ViewController by overriding shouldAutorotateToInterfaceOrientation or supportedInterfaceOrientations.

9
This is under NDA, post on the apple dev forums and I can help.Ben Clayton
Is it better like that? I guess now nobody would care about hurting NDA anymore...stk
its still an NDA, also I would guess this is a common problem and has been fully answered in the dev forums alreadywattson12

9 Answers

31
votes

If your ViewController is a child of a UINavigationController or UITabBarController, then it is the parent that is your problem. You might need to subclass that parent view controller, just overriding those InterfaceOrientation methods as you've shown in your question

EDIT:

Example for portrait only TabBarController

           @interface MyTabBarController : UITabBarController
            {
            }
            @end

            @implementation MyTabBarController

            // put your shouldAutorotateToInterfaceOrientation and other overrides here        
            - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
                return (interfaceOrientation == UIInterfaceOrientationPortrait);
            }

            - (NSUInteger)supportedInterfaceOrientations{ 
                return UIInterfaceOrientationMaskPortrait; 
            } 

        @end
27
votes

Adding to CSmith's answer above, the following code in a UINavigationController subclass allows delegation to the top view controller in the way that I expected this to work in the first place:

- (BOOL)shouldAutorotate;
{
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    if ([[self topViewController] respondsToSelector:@selector(supportedInterfaceOrientations)])
        return [[self topViewController] supportedInterfaceOrientations];
    else
        return [super supportedInterfaceOrientations];
}
8
votes

Here's another alternative to CSmith's approach.

If you want to replicate the pre-iOS 6 behaviour where all the views in the navigation stack / tab bar have to agree on an allowable set of orientations, put this in your subclass of UITabBarController or UINavigationController:

- (NSUInteger)supportedInterfaceOrientations
{
    NSUInteger orientations = [super supportedInterfaceOrientations];

    for (UIViewController *controller in self.viewControllers)
        orientations = orientations & [controller supportedInterfaceOrientations];

    return orientations;
}
2
votes

Try to add this category:

@interface UINavigationController(InterfaceOrientation)

@end

@implementation UINavigationController(InterfaceOrientation)

- (NSUInteger) supportedInterfaceOrientations {
    if (self.viewControllers.count > 0)
        return [[self.viewControllers objectAtIndex:0] supportedInterfaceOrientations];
    else
        return UIInterfaceOrientationMaskAll;
}

@end
1
votes

For people using UINavigationController and Swift, you can add this extension to your project. After that, navigation controllers delegate the control to their child controller.

extension UINavigationController {
    override public func supportedInterfaceOrientations()
    -> UIInterfaceOrientationMask {
        if let ctrl = topViewController {
            return ctrl.supportedInterfaceOrientations()
        }
        return super.supportedInterfaceOrientations()
    }

    override public func shouldAutorotate() -> Bool {
        if let ctrl = topViewController {
            return ctrl.shouldAutorotate()
        }
        return super.shouldAutorotate()
    }
}
0
votes

Further addition to @CSmith and @EvanSchoenberg.

If you have some views that rotate, and some views that don't, you must create a custom instance of the UITabBarController, yet still let each UIViewController decide.

- (BOOL)shouldAutorotate;
{
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    UIViewController * top;
    UIViewController * tab = self.selectedViewController;
    if([tab isKindOfClass:
        ([UINavigationController class])]) {
        top = [((UINavigationController *)tab)
                 topViewController];
    }

    if ([top respondsToSelector:@selector(supportedInterfaceOrientations)])
        return [top supportedInterfaceOrientations];
    else
        return [super supportedInterfaceOrientations];
}
0
votes

@Alvivi's answer updated for Swift 4.

extension UINavigationController {

    // Look for the supportedInterfaceOrientations of the topViewController
    // Otherwise, viewController will rotate irrespective of the value returned by the ViewController
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if let ctrl = self.topViewController {
            return ctrl.supportedInterfaceOrientations
        }
        return super.supportedInterfaceOrientations
    }

    // Look for the shouldAutorotate of the topViewController
    // Otherwise, viewController will rotate irrespective of the value returned by the ViewController
    override open var shouldAutorotate: Bool {
        if let ctrl = self.topViewController {
            return ctrl.shouldAutorotate
        }
        return super.shouldAutorotate
    }
}
-1
votes

@Shimanski Artem's answer is good, but I think using the topmost (currently visible) controller is better solution:

@interface UINavigationController(InterfaceOrientation)

@end

@implementation UINavigationController(InterfaceOrientation)

- (NSUInteger) supportedInterfaceOrientations {
    if (self.viewControllers.count > 0){
        return [[self.viewControllers objectAtIndex:[self.viewControllers count] - 1] supportedInterfaceOrientations];
    }
    return UIInterfaceOrientationMaskAll;
}

@end
-2
votes

As an alternate option, in case you want to preserve pre-iOS6 rotation functionality in your app:

Here's a helpful bit of code on github that swizzles the method calls for iOS6 so that rotation works like it did on iOS4/iOS4. This really helped me, as I'm supporting a legacy app that really micro-manages its rotations. It would have been a lot of work to implement the changes required for iOS6. Kudos to the user who posted it.

https://gist.github.com/3725118