iOS 6.0 restrict auto rotation within a navigation controller? - ios

What else should I do?
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) toInterfaceOrientation
{
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
-(BOOL)shouldAutoRotate
{
return NO;
}
My viewController still rotates.
It is embedded in a navigation stack.
If I subclass UINavigationController, and implement the same portrait-only templates there, and I embed my viewController in that tweaked navigationController, than it works, but I have no intention to rewrite my code everywhere a UINavigationController appears.
What is the best practice here?

ORIGINAL ANSWER: No need to subclass - just do a category like I described in my solution here:
Top-home button portrait orientation in iOS6 simulator not working
Basically, for iPhone the UINavigationController allows rotation for everything except "top home button portrait", for iPad it allows everything.
So either you do a category forwarding the decision to the currently active view controller or something static like
UINavigationController-Rotation.h:
#interface UINavigationController (Rotation)
#end
UINavigationController-Rotation.m:
#import "UINavigationController-Rotation.h"
#implementation UINavigationController (Rotation)
#pragma From UINavigationController
- (BOOL)shouldAutorotate {
return NO;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
#pragma -
#end
UPDATE: As Javier Soto pointed out, this might lead to undefined behavior if there is a second category doing the same. In that case, subclassing might be a better solution.
In a situation where you know there is no other category doing the same I still consider this a working, low effort, local and pragmatic solution. I am not religious about that. Decide yourself.

You should inherit from UINavigationController and use your custom one everywhere. It's not that much work (just search for occurrences of UINavigationController in your code). This will turn out to be much more flexible cause you'll be able to customize other things if necessary.
NEVER do it in a category that overrides methods in the main class like that other response suggests.

Related

Restrict/Force only some view controllers to specific orientations when using UITabBarController and/or UINavigationController

I searched the web high and low, but couldn't find a definitive answer on this. What's the best way to have only one UIViewController support landscape mode when it's embedded in a UINavigationController, which itself is part of a UITabBarController?
Most solutions, like this one, suggest overriding
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
in the AppDelegate. This works somewhat, but when returning from the only ViewController which supports landscape mode, all the other (non-landscape ViewControllers) will be in landscape orientation as well. They don't keep their portrait orientation.
I've seen apps getting this right, so I know that it must be possible somehow. For instance, movie player apps are often portrait-only, but the actual movie player view is presented modally in forced-landscape mode. Upon dismissing the modal viewcontroller, the underlying viewcontroller is still correctly in portrait orientation,
Any hints?
Here's my conclusion after a lot of research:
First, I tried implementing
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window;
in the AppDelegate as described here, which worked for most cases. But apart from feeling quite "hacky", there was one serious gotcha: The workaround for modally displayed view controllers (see section "A small problem with modal controllers") breaks down when, for instance, displaying an AVPlayerViewController because it implements its own dismiss method and you can't hook into it to set self.isPresented (unfortunately, viewWillDisappear: is too late).
So I went with the alternative approach of using subclasses for UITabBarController and UINavigationController which feels much cleaner and only slightly more verbose:
CustomNavigationController
CustomNavigationController.h
#import <UIKit/UIKit.h>
#interface CustomNavigationController : UINavigationController
#end
CustomNavigationController.m
#import "CustomNavigationController.h"
#implementation CustomNavigationController
- (BOOL)shouldAutorotate
{
return [self.topViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
#end
CustomTabBarController
CustomTabBarController.h
#import <UIKit/UIKit.h>
#interface CustomTabBarController : UITabBarController
#end
CustomTabBarController.m
#import "CustomTabBarController.h"
#implementation CustomTabBarController
- (BOOL)shouldAutorotate
{
return [self.selectedViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations
{
return [self.selectedViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
#end
After that it's just a matter of adding the following code to your UIViewControllers:
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
// Presents the `UIViewController` in landscape orientation when it is first displayed
return UIInterfaceOrientationLandscapeLeft;
}
- (NSUInteger)supportedInterfaceOrientations {
// Allows all other orientations (except upside-down)
return UIInterfaceOrientationMaskAllButUpsideDown;
}
Now you may decide the preferred and supported orientations on a per-view-controller-basis. Note that I didn't implement shouldAutorotate in my view controllers because it defaults to YES, which is what you want if your view controllers should be forced to a certain orientation (yes, they should autorotate to the only supported orientation).
The call chain goes something like this:
The CustomTabBarController, being the window's rootViewController, is asked for supported/preferred orientations
The CustomTabBarController in turn asks its selectedViewController (view controller of the currently selected tab), which happens to be my CustomNavigationController
The CustomNavigationController asks the embedded topViewController, which is finally the actual UIViewController implementing the methods above
You still need to allow all device orientations in your build target. And of course, you need to update your storyboards/xibs/classes to use these subclasses instead of the standard UINavigationController and UITabBarController classes.
The only downside to this approach is that, at least in my case, I had to add these methods to all of my view controllers to make most view controllers portrait-only and some landscape-capable. But IMHO it's definitely worth it!
You should write the above code to the UINavigationController subclass. You should define your application orientation and separate your view controller orientation using if statements.
-(BOOL)shouldAutorotate {
if ([[self.viewControllers lastObject] isKindOfClass:[UIViewController class]] ) {
return [[self.viewControllers lastObject] shouldAutorotate];
}
return NO;
}
- (NSUInteger)supportedInterfaceOrientations {
if ([[self.viewControllers lastObject] isKindOfClass:[UIViewController class]]) {
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}

disable auto-rotate in someone else's viewcontroller

I'm using navigationController to push someone else's view controller. I can use shouldAutorotate to disable rotation in my view, but this doesn't work for the pushed view, which was implemented by someone else and I can not touch. Is there any way to achieve this? for example by disabling any rotation in any subview of my navigation controller?
Thanks!
Push your own subclass of the vc, one that implements supportedInterfaceOrientations like this:
// .h
#import "ThirdPartyVC.h"
#interface MyNonRotatingThirdPartyVC : ThirdPartyVC
#end
// .m
#implementation MyNonRotatingThirdPartyVC
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
Allocate and present a MyNonRotatingThirdPartyVC instead of the original
.

Setting orientation in iOS 7

I have th e following code in my uiviewcontroller.m file:
-(BOOL)shouldAutoRotate
{
return YES or NO;
}
I have tried both YES and NO but my view controller still rotates? I am using iOS 7 and the other iOS solutions I found aren't working for me either.
It probably happens because your controller instantiated as child of UINavigationController in view hierarchy. The UINavigationController does not query child controllers if they want to be rotated or not.
I had the same issue; I wanted to disable autorotation, so all hierarchy of particular UINavigationController is locked in Portrait. I ended with this class:
#implementation FixedOrientationNavigationController
- (BOOL)shouldAutorotate {
return NO;
}
#end
which I put instead of UINavigationControllr class in Storyboard for hierarchies which I need to lock Portrait. Just this, I do not need to implement shouldAutorotate in each controller.
You may also check this link: Orientation Respectful UINavigationController, it tries to implement "orientation respectful" UINavigationController.
It works, but in some cases it leads to weird results, for example, when user rotate to Landscape and then go back to the controller which should only support Portrait.
You can also set the orientation by clicking on project name and then general ,here you can set the orientations you want and set
- (BOOL)shouldAutorotate {
return NO;
}
Hope you got.

Prevent rotation of ABPersonViewController and ABNewPersonViewController on iOS6

In direct reference to this question:
How to stop rotation of ABPersonViewController & ABNewPersonViewController in Landscape mode in iphone
How can I prevent this screen from rotating in iOS 6 when I am not the one pushing the view controller? Scenario being I create a new contact, and the user then presses either the 'Create new contact' or 'Add to existing contact' buttons. The screen produced is the ABNewPersonViewController but because I do not have direct access to the rotation methods, I am unable to prevent it from rotating.
Screenshot:
The above image is taken from a subclass of the ABUnknownPersonViewController, in this subclass the only functionality I have implemented is to override the rotation methods as follows:
- (BOOL)shouldAutorotate
{
return NO;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
if(toInterfaceOrientation == UIInterfaceOrientationPortrait)
return YES;
return NO;
}
However, the issue is, I cannot then subclass the ABNewPersonViewController screen that is pushed when one of the buttons in the above image is pressed to override the rotation on iOS 6. Any ideas on how I can legitimately get access to these buttons OR override the rotation on the screen that is pushed to prevent it from doing so?
UPDATE 1:
I attempted to create a category on the ABNewPersonViewController and ABUnknownPersonViewController that overrode the rotation methods (not ideal, I know) and then globally imported, but this did not work. Other than this, I am completely stuck for ideas on how to override this behaviour. Any suggestions?
UPDATE 2:
Is it possible to gain a reference to the buttons in that UITableView and override the methods they call? Or is this in violation of Apple terms by accessing private APIs? Trying to investigate this approach so far and not really getting anywhere.
On iOS 6, rotation handling has changed. There are two options to prevent the rotation:
You can set your whole app to only support portrait orientation in your Info.plist.
You can override a method in your application delegate:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
return UIInterfaceOrientationMaskPortrait;
}
As that method is called on your delegate whenever the orientation changes or a new view controller is pushed, you can even use it to temporarily disable landscape display:
// In AppDelegate.h:
#property (nonatomic) BOOL portraitOnly;
// In AppDelegate.m:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
return _portraitOnly ? UIInterfaceOrientationMaskPortrait : UIInterfaceOrientationMaskAllButUpsideDown;
}
// to switch to portrait only:
((AppDelegate *)[UIApplication sharedApplication].delegate).portraitOnly = YES;
// to switch back to allowing landscape orientations as well:
((AppDelegate *)[UIApplication sharedApplication].delegate).portraitOnly = NO;
Both methods are perfectly acceptable for App Store submission, as these only use published and documented behavior.
Going off of Tammo Freese answer. I assume the view that needs to be allow landscape is lower in the view stack than your custom ABUnknownPersonViewController. If so in your ABUnknownPersonViewController subclass over add this
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
((AppDelegate *)[UIApplication sharedApplication].delegate).portraitOnly = YES;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:(BOOL)animated];
((AppDelegate *)[UIApplication sharedApplication].delegate).portraitOnly = NO;
}
In iOS 6 what orientations view controllers are allowed to rotate to is controlled by the top-most full screen view controller, and autorotation methods are no longer called down the view controller hierarchy.
Instead of creating a subclass of the ABPersonViewController, create a subclass of UINavigationController with this code:
- (BOOL)shouldAutorotate
{
return NO;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
Use this new subclass when creating the UINavigationController for the ABPersonViewController you are presenting. If the UINavigationController is not being presented modally (and is instead nested in a UITabBarController or another view controller), you will need to also put these methods in that view controller as well.

Prevent autorotate for one view controller?

My app can autorotate but I need one of the views to only show in portrait mode and don't know how to achieve this.
I tried this (among other things) but the view in question still rotates:
// ViewController.m
-(BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
Can someone kindly point out what I'm doing wrong? Thanks.
-edit-
It's for iOS 6.1
When a UINavigationController is involved, create a category on the UINavigationController and override supportedInterfaceOrientations.
#import "UINavigationController+Orientation.h"
#implementation UINavigationController (Orientation)
- (NSUInteger)supportedInterfaceOrientations {
return [self.topViewController supportedInterfaceOrientations];
}
- (BOOL)shouldAutorotate {
return YES;
}
#end
Now, iOS containers (such as UINavigationController) do not consult their children to determine whether they should autorotate.
How to create a category
1. Add a new file (Objective c- category under cocoa touch)
2. Category : Orientation on UINavigationController
3. Add the above code to UINavigationController+Orientation.m
Swift 3 version the accepted answer:
extension UINavigationController {
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
// Change `.portrait` to whatever your default is throughout your app
return topViewController?.supportedInterfaceOrientations ?? .portrait
}
open override var shouldAutorotate: Bool {
return true
}
}
As per the documentation.
A view controller can override the supportedInterfaceOrientations method to limit the list of supported orientations.
So we need to override shouldAutorotate and supportedInterfaceOrientation to target view controllers.
Typically, the system calls this method only on the root view controller of the window or a view controller presented to fill the entire screen.
This will work if you have very simple configuration like your target view controller is the rootViewController of window or being presented covering whole screen.
In case when configuration of target view controller is complex like embedded in some other container view controllers.
child view controllers use the portion of the window provided for them by their parent view controller and no longer participate directly in decisions about what rotations are supported.
So may be default implementation of these container view controllers not asking there children for there supportedInterfaceOrientation preference.
So to allow our target child view controller to specify there supportedIntefaceOrientation we need to tell there container view controller to do so.
You can also check my previous answer here.
and Understanding UIViewController rotation when embed in Container View Controllers.

Resources