I have a UINavigationController which creates an instance of UIViewController and sets another UIViewController as it's root. I then present the navigation controller and everything works.
The problem comes when the user rotates the device. All my controllers (including root and excluding UINavigationController) implement the shouldAutorotate and return FALSE.
Somehow, my views still rotate. We come to the centre of my problem. I create and present the navigation controller like so:
AwesomeViewController *controller = [[AwesomeViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:navController
animated:YES
completion:NULL];
It is pretty straight forward as you can see.
The navController is missing its shouldAutorotate set to FALSE and that's why it's rotating, am I correct?
How to lock it in portrait orientation?
Can it be done without making a ridiculous subclass like this:
#interface LockedRotationNavigationController : UINavigationController
#end
#implementation LockedRotationNavigationController
- (BOOL)shouldAutorotate { return NO; }
#end
What I'm actually looking for is how to disable rotation of the UINavigationController without subclassing it?
The property is readonly so I'm out of ideas on how to do it.
To disable rotations globally in your app in Info.plist expand Supported interface orientations and remove Landscape items to make your application only run in portrait mode.
Or you can just select supported orientations in Xcode like this:
You can also lock the rotation programmatically by implementing shouldAutorotateToInterfaceOrientation method in your view controller.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
Related
I have a UIViewController, which is presented modally (full screen) and I would like to disable autorotation in that view. I do not want to restrict it to landscape or portrait, just would like it to stay in whatever orientation it was originally presented.
On iOS 6 it was sufficient to just override the method:
- (BOOL)shouldAutorotate {
return NO;
}
And it did exactly what I wanted. On iOS 7 however, this seems to have no effect. The method does get called, but the return value seems to be ignored by the OS - it auto rotates no matter what.
The documentation does not mention any changes to this method. How can I achieve the desired effect on iOS 7?
Edit: the view controller is being presented (not pushed!) by a UINavigationViewController:
[self.navigationController presentViewController:vc animated:YES completion:nil];
Solution:
As odd as it may seem, but this solution was not published in the numerous existing questions on this topic. On iOS 7 it seems the answer the UINavigationController gives to
shouldAutorotate is what the OS acts on. We need to subclass UINavigationController to modify its behaviour.
When dealing with a regular navigation stack it is indeed sufficient to just use [self.topViewController shouldAutorotate], but when there is modal view, it resides in self.presentedViewController, not self.topViewController. Thus the full solution looks like:
- (BOOL)shouldAutorotate {
UIViewController *vc;
if (self.presentedViewController) vc = self.presentedViewController;
else vc = [self topViewController];
return [vc shouldAutorotate];
}
So I just tried you code and it worked which leads me to believe that you are presenting your UIViewController in a UINavigationController. For whatever reason, iOS 7 changed how UINavigationController handle rotations.
The easiest solution is to create a subclass of UINavigationController that overrides the shouldAutorotate method and returns the value from the topViewController.
#interface CustomNavigationController : UINavigationController
#end
#implementation CustomNavigationController
- (BOOL)shouldAutorotate
{
return [[self topViewController] shouldAutorotate];
}
#end
So instead of doing this, where viewController is your object that return NO for shouldAutorotate.
UINavigaitonController *navController = [UINavigationController alloc] initWithRootViewController:viewController];
[self presentViewController:navController animated:YES completion:nil];
You would use the CustomNavigationController instead
CustomNavigationController *customNavController = [CustomNavigationController alloc] initWithRootViewController:viewController];
[self presentViewController:customNavController animated:YES completion:nil];
#import <objc/message.h>
-(void)viewDidAppear:(BOOL)animated{
objc_msgSend([UIDevice currentDevice], #selector(setOrientation:), UIInterfaceOrientationPortrait );
}
see How do I programmatically set device orientation in iOS7?
But take the case you are using this method because it is a private API, and your app may be rejected by Apple. So, maybe is better to set orientation from Project details -> General -> Deployment Info tab, choose Landscape Left and Landscape Right choice only. This can be a better approach, if all your views need only one kind of orientation.
I am working on iOS 6.1 and 7.0. The behavior is the same for both of them. The root view controller is UIVeiwController. It has a property UINavigationViewController which manages all the displayed controllers in my app. I have subclassed UINavigationController and overloaded shouldAutorotate and supportedInterface rotations methods to get correct orientations of each controller pushed in it.
What I want: Push in navigation 3 controllers, the second controller should be only in portrait orientation.
What I get: When I am pushing second controller it is still in landscape mode, despite it's supportedInterfaceOrientation method returns Portrait mode, then push third controller and put simulator or device in landscape mode and pop to my second controller, here is most interesting it goes away from the screen...
My guess Apple documentation says that only most top view controller will be notified about rotations, so when I make navigation controller root it works correct when I make pop from third controller to second but still doesn't work when pushing from first to second. Any way I need to solve this issue with my structure when navigation controller is only a property of root view controller.
Here is a link to project https://github.com/Trubianov/Navigation.git
The problem is that the new workflow makes rotation methods to be checked only once when the controller is presented, that's why it's not working.
When pushing things inside a UINavigationController (or any other UIViewController container) you can decide on the child UIViewController what orientation it supports, and the OS will make it work for you.
My suggestion: unless you have some custom code that you MUST have in your UINavigationController subclass, you should remove it entirely and move the rotation checks to the child view controllers you are presenting
Here is the solution. If you put UIViewController to window.root, and your navigation controller is just a property of the root controller, than your navigation won't get appropriate callbacks from UIApplication, so u MUST add navigation as child view controller to your root controller.
My code snippet:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
MyNavigationController *navController = [[MyNavigationController alloc] initWithRootViewController:[ViewController1 new]];
MainViewController *main = [MainViewController new];
main.myNavigationController = navController;
[main addChildViewController:navController];
self.window.rootViewController = main;
[self.window makeKeyAndVisible];
return YES;
}
Also is needed to override following methods in MainViewController.
- (BOOL)shouldAutorotate
{
return [self.myNavigationController.topViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations
{
return [self.myNavigationController.topViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.myNavigationController.topViewController preferredInterfaceOrientationForPresentation];
}
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.
I have an iPad application which I have created using storyboards. I have created another single viewController which I have created using a separate .xib file. This viewController I need to call from the main application, and then later dismiss to return back to the main application. I am able to do this so far. My problem is that because I am using a Navigation Controller to call this secondary view controller, I am unable to load this view controller in landscape mode. I am only able to load it in portrait mode. Based on going through this forum, and from whatever research that I have done, I have learned that I need to subclass the navigation controller, and then that is how I will be able to load this secondary view controller in landscape mode.
I have included the following methods in my secondary view controller (NextViewController), but it has no effect:
-(BOOL)shouldAutorotate
{
return YES;
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
Here is the code in the calling viewController (MainViewController), which calls NextViewController, which in turn is appearing in portrait mode, instead of the desired landscape mode:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_nextView = [[NextLandscapeViewController alloc] initWithNibName:#"NextLandscapeViewController" bundle:nil];
[_nextView setDelegate:(id)self];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:_nextView];
[self presentViewController:navigationController animated:YES completion:nil];
}
As I pointed out, the solution that I need is to subclass the Navigation Controller, but I honestly have never done this before, and nor do I know how to do it. Can someone show me how to do it so that I can call NextViewController, and have it displayed in landscape mode?
Thanks in advance to all who reply.
For subclass from Navigation Controller for orientation, you can try this code (as example):
// .h - file
#interface MyNavigationController : UINavigationController
#end
// .m - file
#import "MyNavigationController.h"
#implementation MyNavigationController
-(BOOL)shouldAutorotate
{
return [self.topViewController shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
#end
upd: (This code work on ios6)
When the application is in landscape mode (which I plan to force), displaying a modal view causes the parent view to rotate to portrait mode. If I set the return value of shouldAutoRotateToInterfaceOrientation to NO, the parent does not rotate, however the modal then slides in from the side and displays sideways. Below is the code that reveals the modal.
- (IBAction)loadExistingGame:(id)sender {
SavedGamesTableViewController *savedGames = [[SavedGamesTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
savedGames.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:savedGames animated:YES];
[savedGames release];
}
As per request here is the contents of the shouldAutoRotate method of the SavedGamesTableViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Override to allow orientations other than the default portrait orientation.
return YES;
}
Ok I figured out what needed to be done to fix it. The plist file that contains a list of the possible orientations needs to be limited to a single landscape view. The parent to the modal table view needs to have the shouldAutoRotateToInterfaceOrientation method return YES only if the orientation matches the only orientation in the plist file.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return interfaceOrientation = UIInterfaceOrientationLandscapeRight;
}
the modal viewcontroller should return NO for the same method.
Based on
When the application is in landscape
mode (which I plan to force),
displaying a modal view causes the
parent view to rotate to portrait
mode.
and
As per request here is the contents of
the shouldAutoRotate method of the
SavedGamesTableViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Override to allow orientations other than the default portrait orientation.
return YES;
}
So what you're saying is that the parent view controller is not yet set to force only using landscape orientation, and when you show a modal view that is set to allow all orientations, you're wondering why your parent view rotates to portrait when you rotate the device to portrait? I don't understand your question... aren't you saying that parent view controller is currently set to allow rotation to portrait? Isn't this behaviour exactly what should happen?
I had a similar problem when bringing up a modal mail view. Forcing the rotation didn't work for me, but calling presentModalViewController on the application's main view controller rather than a child view controller solved the issue.
I was seeing the same behavior; in my case the problem was I had implemented shouldAutorotateToInterfaceOrientation to return YES unconditionally for the parent view controller but NOT for the presented modal view controller. So I suspect Shaggy Frog's comment is the key: whether you want to force landscape mode or not, you need to make sure that the two view controllers' shouldAutorotateToInterfaceOrientation implementations agree or weirdness will ensue.
UIViewController *vc = /* create view controller */;
UINavigationController *nc = nil;
if (IOS_VERSION_LESS_THAN_6_0) {
nc = [[MyCustomNavigationControllerSupportingAllOrientations alloc] initWithRootViewController:vc];
} else {
nc = [[UINavigationController alloc] initWithRootViewController:vc];
}
[self.navigationController presentModalViewController:nc animated:YES];
On iOS6 I use a UINavigationController.
On pre-iOS6 I subclass UINavigationController, like this:
#interface MyCustomNavigationControllerSupportingAllOrientations : UINavigationController
#end
#implementation MyCustomNavigationControllerSupportingAllOrientations
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
#end