I have a UIViewController that only works in landscape, i.e. is configured with
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationLandscapeLeft;
}
I have a custom UIView (not View Controller) with its own NIB. I load it in the view controller with
- (void)viewDidLoad
{
[super viewDidLoad];
ISMainView *customView = [[[NSBundle mainBundle] loadNibNamed:#"ISMainView" owner:nil options:nil] lastObject];
[self.view addSubview:customView];
}
When the view loads it's displayed rotated by 90 degrees, as if it wants to be in portrait orientation.
Any idea what's wrong? A UIView doesn't know anything about its own orientation, right? So the problem must be with the View Controller?
So it appears the problem was being caused by
-(BOOL)shouldAutorotate {
return NO;
}
I'm not sure why, but turning off shouldAutorotate prevented the subview from being set to the proper device orientation, even though the device never rotates. Odd.
Related
How can I keep the orientation of a view completely unchanged when the interface orientation changes, independently from the other views?
For example, if I have a tall blue view in portrait mode, I would like the view to remain oriented the same way when the interface switches to landscape mode. If I have any other views aside from this view, the other views should reorient as usual, and, also, the status bar should get reoriented as well. The only view that should not get reoriented is this blue view.
All I could do so far with Auto Layout was to resize and reorient the view, which is what I don't want to happen.
- (BOOL)shouldAutorotate;
- (NSUInteger)supportedInterfaceOrientations;
This should help you.
Also you may try to use
- (void)viewWillAppear:(BOOL)animated {
[[UIDevice currentDevice] setValue:#(UIInterfaceOrientationLandscapeLeft) forKey:#"orientation"];
}
Did you try overriding the NavigationController Class.
In that, Implement the supportedInterfaceOrientations methods.
-(NSUInteger)supportedInterfaceOrientations
{
UIViewController *vc = [[self viewControllers] lastObject];
Class clsSupportsSpecificOri = NSClassFromString(#"REQUIRED_CLASS");
if ([vc isKindOfClass:clsSupportsSpecificOrientation])) {
return UIInterfaceOrientationMaskLandscapeLeft;//Set your required orientation
}
else {
return UIInterfaceOrientationMaskAll;
}
}
This might helps you. As the supportedInterfaceOrientation method gets called every time a navigation occurs.
I know this has been asked and discussed a lot here, but I've spent the last few days scouring the internet for different solutions, and no matter what I try - nothing seems to work.
EDIT:
Tried this solution as well, still no luck...
I have an iOS7 app (I currently don't care for support for iOS6, but it'll be nice to have) that has a root view controller with a menu that slides from the side (this one, to be specific) and lets you switch between different screens. The selected screen is loaded into a navigation controller, and there's a "modal" type segue between the navigation controller and the screens' view controllers (except for the screen that appears first, which has a relationship with the root view controller). I also tried this with "push" segues, same result.
one of these screens has to display only in landscape mode, the others only in portrait.
for this purpose, in my root view controller I've implemented the following:
- (void)awakeFromNib
{
self.contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"contentController"];
self.menuViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"menuController"];
}
-(NSUInteger)supportedInterfaceOrientations
{
if (self.contentViewController)
return [self.contentViewController supportedInterfaceOrientations];
return UIInterfaceOrientationMaskPortrait;
}
-(BOOL)shouldAutorotate
{
return YES;
}
In my navigation view controller I have implemented the following:
-(BOOL)shouldAutorotate
{
return YES;
}
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
and in each of the portrait screens' view controllers I've implemented:
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
and in the landscape screen:
- (NSUInteger) supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationLandscapeLeft;
}
The first screen of the app is a portrait one. So what happens is, the app loads in portrait mode and doesn't rotate to any other orientation (which is great). BUT! Once I load the landscape screen, it loads in portrait mode, and only when I rotate the device to landscape mode, it rotates and locks into landscape mode. And once I switch back to a portrait screen, it loads in landscape mode, and only when I rotate the device to portrait, is rotates and locks into portrait mode, and for some reason makes the screen VERY narrow...
The closest I've ever gotten to a decent solution was implementing this in the landscape screen's view controller:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
CGRect screenRect = [[UIScreen mainScreen] bounds];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft];
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation(degreesToRadian(270));
landscapeTransform = CGAffineTransformTranslate(landscapeTransform, 0.0, 0.0);
[self.navigationController.view setTransform:landscapeTransform];
self.navigationController.view.bounds = CGRectMake(0.0, 0.0, screenRect.size.height, screenRect.size.width);
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
CGRect screenRect = [[UIScreen mainScreen] bounds];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation(degreesToRadian(0));
landscapeTransform = CGAffineTransformTranslate(landscapeTransform, 0.0, 0.0);
[self.navigationController.view setTransform:landscapeTransform];
self.navigationController.view.bounds = CGRectMake(0.0, 0.0, screenRect.size.width, screenRect.size.height);
}
#define degreesToRadian(x) (M_PI * (x)/180.0)
And changing this view controller's supported and preferred orientations to portrait. Which basically makes the whole app lock in portrait mode.
Although it looks fine, it seems kinda sketchy and I would much rather have a "clean" solution, and support landscape left AND right. Any ideas on what I'm missing here?
If you need me to provide more code, just tell me.
Thanks!! :)
Ok, just in case this interests anyone, this was my solution, using the accepted answer here:
The thing I was missing was in my approach - The landscape VC can't be under the same root VC as the portrait ones, it needs to be or have its own root VC, which is in landscape.
So first, I separated the landscape VC from the rest in the storyboard, now it's completely independent. Next, I created a "view controller switch" method, which basically loads a new controller, sets it to be the root controller, and releases the previous root controller:
+(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;
//set the new controller as the root controller
[[[UIApplication sharedApplication].delegate window] setRootViewController:VControllerToLoad];
//kill the previous view controller
[VControllerToRelease.view removeFromSuperview];
}
In the landscape VC I added this code:
-(BOOL)shouldAutorotate
{
return YES;
}
And whenever I need to present the landscape VC or go back the the portrait VCs, I just use the VC switch method. For example:
[AppUtils loadController:landscapeViewController andRelease:portraitNavigationController];
That's it! Now everything works like a charm! :)
So I am developing an iPad app that supports only landscape mode except for on one modal view controller. The issue I am having is that once I present the modal view and change the orientation to portrait then dismiss the view, the parent view (which should only support landscape) is in portrait mode until I rotate the device in which it then goes back to landscape and stays that way. I have been beating myself up trying to figure out how to keep the parents view original orientation but haven't been able to find a solution.
I have the following code in my app delegate to allow orientation changes on only that single modal view (GalleryPhotoViewer) :
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
NSUInteger orientations = UIInterfaceOrientationMaskAllButUpsideDown;
if(self.window.rootViewController){
UIViewController *presentedViewController = [[(UINavigationController *)self.window.rootViewController viewControllers] lastObject];
//Support Portrait mode only on Photoviewer
if ([[presentedViewController presentedViewController] isKindOfClass:GalleryPhotoViewController.class] ) {
orientations = UIInterfaceOrientationMaskAll;
}else{
orientations = [presentedViewController supportedInterfaceOrientations];
}
}
return orientations;
}
From the parent class (PhotosViewController) I am calling :
GalleryPhotoViewController *gpView = [GalleryPhotoViewController new];
[self presentViewController:gpView animated:YES completion:nil];
Also in my parent (and other views) I have the following code to disallow portrait mode :
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if(interfaceOrientation == UIInterfaceOrientationPortrait) {
return YES;
} else {
return NO;
}
}
Any ideas on how I can keep the orientation on my parent view? I was thinking about possibly just programmatically changing the orientation on the parent in the viewWillAppear method once the modal is dismissed but then I wouldn't know what the previous orientation was, not to mention I haven't been able to find code to do this regardless for ios6.
EDIT/SOLUTION : So I found a solution and what I ended up doing was leaving the application:supportedInterfaceOrientationsForWindow: code and just adding the UINavigation subclass to the parent view that was presenting the modal view and everything worked as expected and the parent retained its original orientation while the modal was able to change freely.
In my parent :
//To make sure that this view remains in Landscape
#implementation UINavigationController (Rotation_IOS6)
-(BOOL)shouldAutorotate
{
return [[self.viewControllers lastObject] shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
#end
Thanks #matt for the suggestions.
I think the problem is your use of application:supportedInterfaceOrientationsForWindow:. Instead, get rid of that, and start with a UINavigationController subclass and make that the class of the root view controller that is your navigation interface. Then:
In the UINavigationController subclass, return UIInterfaceOrientationMaskLandscape from supportedInterfaceOrientations.
In the presented (modal) view controller, return UIInterfaceOrientationMaskAll from supportedInterfaceOrientations.
i'd like to handle orientation change on an iPad application with one UIViewController and two XIBs, let's say MenuView and MenuViewLandscape.
So, in the willRotateToInterfaceOrientation method of the MenuViewController, how can i change XIB without using another controller for the landscape mode ?
I'm using the following code:
if( toInterfaceOrientation != UIInterfaceOrientationPortrait ){
MenuViewController *landscape = [[MenuViewController alloc]
initWithNibName: #"MenuViewLandscape"
bundle:nil
];
[self setView:landscape.view];
}
else {
MenuViewController *potrait = [[MenuViewController alloc]
initWithNibName: #"MenuView"
bundle:nil
];
[self setView:potrait.view];
}
But when i go to landscape view the XIB the landscape view controls are not properly rotated.
I'm not sure there are any strange side-effects with this implementation, but try something like this and see if it works for you:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
if (UIInterfaceOrientationIsPortrait(orientation)) {
[[NSBundle mainBundle] loadNibNamed:#"MenuView" owner:self options:nil];
if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
self.view.transform = CGAffineTransformMakeRotation(M_PI);
}
} else if (UIInterfaceOrientationIsLandscape(orientation)){
[[NSBundle mainBundle] loadNibNamed:#"MenuViewLandscape" owner:self options:nil];
if (orientation == UIInterfaceOrientationLandscapeLeft) {
self.view.transform = CGAffineTransformMakeRotation(M_PI + M_PI_2);
} else {
self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
}
}
}
This assumes that the File's Owner in your MenuView and MenuViewLandscape XIBs are both set to MenuViewController and that the view outlet is set in both XIBs as well. All of your outlets should be reconnected properly on rotation when using loadNibNamed.
If you are building for iOS 4, you could also replace the loadNibNamed lines with these:
UINib *nib = [UINib nibWithNibName:#"MenuView" bundle:nil];
UIView *portraitView = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
self.view = portraitView;
and
UINib *nib = [UINib nibWithNibName:#"MenuViewLandscape" bundle:nil];
UIView *landscapeView = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
self.view = landscapeView;
These assume that the UIView that you want to display immediately follows the File's Owner and First Responder proxy objects in the XIBs.
Then you just need to make sure the views are rotated properly for the interface orientation. For all of the views that are not in the default portrait orientation, rotate them by setting the transform property of the view and using CGAffineTransformMakeRotation() with the appropriate values as shown in the example above.
The rotation alone might solve your issue without the extra loading of the NIBs. However, loading a whole new instance of a MenuViewController and setting its view to the existing MenuViewController's view might cause some strange behavior with lifecycle and rotation events, so you might be safer trying the examples above. They also save you the trouble of having to create new MenuViewController instances when you only need the view from it.
Hope this helps!
Justin
Perhaps the answer from Jon Rodriguez here will do what you want:
Want to use muliple nibs for different iphone interface orientations
If you have two UIViewController classes, a base class for portrait mode and a subclass of that for landscape mode, you can put almost all the code in the base class. So that gives you most of the advantages of a single view controller class while also allowing you to use other solutions like this:
Easiest way to support multiple orientations? How do I load a custom NIB when the application is in Landscape?
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