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
.
Related
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;
}
In my application I am trying to force a view controller to be portrait only. I am using the code below but nothing is really happening. I am still able to see the screen in landscape as in portrait?
I have enabled landscape (left/right), portrait in my project settings.
What am I do wrong?
ViewController.m
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
If you VC is embedded in a navigation or tab bar controller you need to create a category on that controller. This is an example that allow navigation controller rotate in all ways:
#import "UINavigationController+RotationAll.h"
#implementation UINavigationController (RotationAll)
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAll;
}
#end
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)
I want all my views to be in Portrait mode. This works except when I push a UINavigationController onto another one. In this case the views inside the secondaryNavigationController will adhere to device orientation instead. Here is how I'm calling the UINavigationControllers.
[secondaryNavigationController setNavigationBarHidden:YES];
[[appDelegate secondaryNavigationController navigationBar] setHidden:YES];
[mainNavigationController presentModalViewController:[appDelegate secondaryNavigationController] animated:YES];
All my views implement this method but it doesn't seem to help.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
Careful with:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
Since it has been deprecated on iOS 6.0. At the moment here are some issues with the rotation of UIViewControllers inside UINavigationControllers, or UITabBarControllers. To solve this, in your case, you should either sub-class the UINagivationController or create a category for it (although Apple discourages the second one more than the first). You can use this (this case is for a UITabBarController, but you can understand the logic), to check how to do the sub-classing. You can then do the following for your UIViewControllers:
-(BOOL)shouldAutorotate
{
return YES;
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
Although you are allowing to rotate (returning YES), the UIViewController will always be in Portrait. The logic here is that if you are coming from a UIViewController that's on Landscape, if you were returning NO, your UIViewController, would stay in Landscape.
Here is the full answer that worked for me, building off of iYaniv's comment and Jacky Boy's answer.
I took iYaniv's category and made it a subclass of UINavigationController instead of a category (which is what you should do). Then I replaced all instances of UINavigationController * with myUINavigationController *. Then used Jacky Boy's snippet in all of my UIViewControllers.
/* myUINavigationController.h */
#import <UIKit/UIKit.h>
#interface myUINavigationController : UINavigationController
#end
and
/* myUINavigationController.m */
#import "myUINavigationController.h"
#interface myUINavigationController ()
#end
#implementation myUINavigationController
-(BOOL)shouldAutorotate
{
return [[self.viewControllers lastObject] shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}
#end
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.