Understanding iOS 6 Interface orientation change - iphone

ADDED:
I see that my question is viewed often without upvotes so I decided that you guys do not get what you search. Redirecting you to question that has really nice answer about
How to handle orientation changes in iOS6
Specific demands to orientation changes:
Restricted rotation
Upvotes are welcome :)
I've created a new project from Master Detail template and trying to start it with landscape orientation.
As you know the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
method is deprecated and we must use
- (NSUInteger)supportedInterfaceOrientations
and/or
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
Here's my code:
- (NSUInteger)supportedInterfaceOrientations {
NSLog(#"supported called");
return UIInterfaceOrientationMaskAll;//Which is actually a default value
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
NSLog(#" preferred called");//This method is never called. WHY?
return UIInterfaceOrientationLandscapeRight;
}
As you can see I'm trying to return landscape orientation in preferred method but it is never called.
p.s. documentation states:
Discussion The system calls this method when presenting the view
controller full screen. You implement this method when your view
controller supports two or more orientations but the content appears
best in one of those orientations.
If your view controller implements this method, then when presented,
its view is shown in the preferred orientation (although it can later
be rotated to another supported rotation). If you do not implement
this method, the system presents the view controller using the current
orientation of the status bar.
So, the question is: Why the prefferredOrientation method is never get called? And how should we handle different orientations in different controllers?. Thanks!
P.S don't mark the question as duplicate. I've investigated all similar questions and they do not have answer for mine.

About preferredInterfaceOrientationForPresentation
preferredInterfaceOrientationForPresentation is never called because this is not a "presented" view controller. There is no "presentation" involved here.
"Presented" and "presentation" are not some vague terms meaning "appears". These are precise, technical terms meaning that this view controller is brought into play with the call presentViewController:animated:completion:. In other words, this event arrives only if this is what we used to call a "modal" view controller.
Well, your view controller is not a modal view controller; it is not brought into play with presentViewController:animated:completion:. So there is no "presentation" involved, and therefore preferredInterfaceOrientationForPresentation is irrelevant here.
I'm being very explicit about this because I'm thinking that many folks will be confused or misled in the same way you were. So perhaps this note will help them.
Launch into Landscape
In iOS 6, the "Supported Interface Orientations" key in your Info.plist is taken much more seriously than previously. The solution to your overall problem of launching into a desired orientation is:
Make sure that "Supported Interface Orientations" in your Info.plist lists all orientations your app will ever be allowed to assume.
Make sure that the desired launch orientation is first within the "Supported Interface Orientations".
That's all there is to it. You should not in fact have to put any code into the root view controller to manage the initial orientation.

If you would like launch modal view in Landscape Mode, just put this code in presented view controller
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
UIInterfaceOrientation orient = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsLandscape(orient)) {
return orient;
}
return UIInterfaceOrientationLandscapeLeft;
}
Then, present this controller as usual
UIViewController *vc = [[UIViewController alloc] initWithNibName:nil bundle:nil];
[vc setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[vc setModalPresentationStyle:UIModalPresentationFullScreen];
[self.navigationController presentViewController:vc animated:YES completion:^{}];

There is a very simple answer: You can only change or fix the interface orientation of a modal presented view controller. If you do so i.e. with a Present Modally segue in Interface builder (or the navigation controller method) you can define the allowed orientations with
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait; // for example else an addition of all allowed
}
This event doesn't fire up when you only push a view controller on the navigation Controller ... so : You don't have a BACK button and need a
[self dismissViewControllerAnimated:YES completion: ^(void) {
}];
to close it.

Related

Handling autorotation for one view controller in iOS7

I've read many answers on SO but I can't seem to get autorotation working on iOS7.
I only need one view controller to rotate, so I don't want to set rotation settings in my Info.plist.
As I understand Apple's documentation, a single view controller can override global rotations settings (from Info.plist) by simply overriding two methods. Info.plist is set to only allow Portrait, and my view controller implements the following methods:
- (NSUInteger)supportedInterfaceOrientations
{
NSLog(#"%s", __PRETTY_FUNCTION__);
return UIInterfaceOrientationMaskAllButUpsideDown;
}
- (BOOL)shouldAutorotate
{
NSLog(#"%s", __PRETTY_FUNCTION__);
return true;
}
I'm seeing those NSLog statements upon rotation but nothing rotates.
If I do configure Info.plist with the proper rotation settings, my view will rotate, but not if I try and rely on my view controller.
Not sure if it matters, but the view I'm trying to rotate is from a .xib using auto layout.
Also, my ViewController is being presented modally and is contained in a navigation controller. I've tried just presenting the view controller by itself and that doesn't work. I've also tried adding a category to UINavigationController to get it's autorotation directions from it's topViewController.
In my case, I had a new iOS7 app with about 30 view controllers created already. I needed auto rotation on just a single modal view controller. I didn't want to have to update the preexisting view controllers.
I selected the orientations I wanted in the plist:
Then I added a category to my app delegate on UIViewController:
#implementation UIViewController (rotate)
-(BOOL)shouldAutorotate {
return NO;
}
#end
Then in the single modal view controller I WANTED to rotate I added this method:
-(BOOL)shouldAutorotate {
return YES;
}
I also discovered, that if my view controller wasn't a modal VC I would need to add category methods on UINavigationController instead, for all VCs that were subsequent to the root view controller, as part of the navigation stack of view controllers - similar to this: https://stackoverflow.com/a/20283331/396429
Simple but it work very fine. IOS 7.1 and 8
AppDelegate.h
#property () BOOL restrictRotation;
AppDelegate.m
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if(self.restrictRotation)
return UIInterfaceOrientationMaskPortrait;
else
return UIInterfaceOrientationMaskAll;
}
ViewController
-(void) restrictRotation:(BOOL) restriction
{
AppDelegate* appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
appDelegate.restrictRotation = restriction;
}
viewDidLoad
[self restrictRotation:YES]; or NO
You need to set the plist value to all possible values, then limit them as you see fit (in the Navigation Controllers and TabBar Controllers. From the UIViewController class description:
In iOS 6 and later, your app supports the interface orientations
defined in your app’s Info.plist file. A view controller can override
the supportedInterfaceOrientations method to limit the list of
supported orientations. 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; 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. The intersection of the app’s orientation
mask and the view controller’s orientation mask is used to determine
which orientations a view controller can be rotated into.
I've faced such problem - had only one landscape view in my app.
I've used below code to to handle that.
#import "objc/message.h"
-(void)viewWillAppear:(BOOL)animated{
objc_msgSend([UIDevice currentDevice], #selector(setOrientation:), UIInterfaceOrientationLandscapeLeft);
}
I know this is old but I ended up in a more unique situation where we have 50+ ViewController all over the app that I refused to go through and modify and support the same orientation in all of them but one or 2. Which brings me to my answer. I created a UIViewController category that overrides - (BOOL)shouldAutorotate to always return NO or YES depending on device type etc. (this can be done with supported interface orientations too). Then on the ViewControllers I wanted to support more then just portrait, I swizzled shouldAutorotate to return YES. Then forced the orientation change when the view is dismissed on the parent ViewControllers viewWillAppear method using:
[[UIDevice currentDevice] setValue:#(UIInterfaceOrientationPortrait) forKey:#"orientation"].
When all was said and done, I accomplished everything I wanted on a few ViewControllers with < 30 lines of code using a macro for swizzling. Had I done it by replacing shouldAutorotate and supportedInterfaceOrientations on all of the VC's in the application I would have ~250 extra lines of code. and a lot of grunt work adding it in the first place.

issue with iOS 6 autorotation

I have a viewcontroller in which I wanted it to be presented only in portrait, so I did the following in iOS 6:
-(BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
however when I rotate the device, it still turns it to landscape. Any idea where else to check this? I put a break point and it hits supportedInterfaceOrientations, but it still rotates
Do you have a navigation controller? The way that iOS6 determines what can be autorotated has changed. It is correctly asking supportedInterfaceOrientations for your view controller but it is probably asking "shouldAutorotate" to another element in your navigation stack hierarchy and accepting that answer. If your navigationController/tabviewController returns yes to this question then it won't consult with your view controller.
You should also provide the apps supported orientations in the app delegate:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
return UIInterfaceOrientationMaskPortrait;
}
Make sure you add the root view controller properly (not adding it as a subview), but using the following:
[window setRootViewController:myVC];
Also if your view controller is inside a UINavigationController, you should use this category for the navigationcontroller:
#implementation UINavigationController (autorotate)
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
#end
In iOS 6, only the root view controller of the top most full screen controller is asked about rotation. This includes UINavigationController, this class does not ask it's view controllers, it responds directly. Apple now suggest subclassing UINavigationController to override supportedInterfaceOrientations's output.
Make sure that your project settings and info.plist have only portrait orientation selected as they have a higher priority than the app delegate when checking for supported orientations

Storyboards, Segues and Different views for different orientations

I have been working on iOS5 and have been struggling with storyboards, segues and adjusting the views for different orientations. After going through some Apple's help for the past couple of days; I have not found one place for my answers, I have come to the following conclusions that I will articulate very carefully and I hope someone can tell me if I am correct:
1- Using a storyboard does not mean you have to use segues (yes this is confusion for a beginner).
2- You can only use segues between VCs that belong to a Navigation controller.
3- On the other hand, having a Navigation controller does not necessarily imply that you have to use a segue from one VC to another VC (yep!). So if you are trying to go from one VC to another VC (regardless of whether the first VC belongs to a Nav Controller or not),you can do the following:
Assume you have a WhiteVC class and a YellowVC class and you placed a button on the WhiteVC view (in the storyboard and changed the class of the generic VC to WhiteVC) and linked it to an IBAction in the WhiteVC class. This would be the code you would implement, in WhiteVC.m, to be able to click that button and have the YellowVC view appear:
-(IBAction)GoToYellow
{
YellowViewController *YellowVC=[self.storyboard instantiateViewControllerWithIdentifier:#"myYellowVC"];
[self presentViewController:YellowVC animated:YES completion:nil];
}
To return from the YellowVC to the WhiteVC, you would then create another button on the YellowVC view (follow same process as above) and use the following code:
-(IBAction)ReturnToWhiteVC
{
[self dismissViewControllerAnimated:YES completion:nil];
}
This will dismiss the YellowVC and return to the White VC.
4- It is not recommended to have one VC own (instantiate) multiple views - for different orientations for example- it is better to have a separate VC for each view. It is ok though to have VCs instantiate other VCs as part of the MVC.
5- Now all of this came about because I was building an app that has a Nav Controller but its root VC will be different (different VCs with different Views) depending on whether it is landscape or portrait. So I could either instantiate the VCs programmatically (like #3 above) which worked well. Or, if I wanted to do the transition using segues, the apple help docs provides the following code:
BOOL isShowingLandscapeView;
- (void)awakeFromNib
{
isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
[self performSegueWithIdentifier:#"AlternateView" sender:self];
isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && isShowingLandscapeView)
{
[self dismissViewControllerAnimated:YES completion:nil];// breakpoint shows that this is triggered
isShowingLandscapeView = NO;
}
}
The above code obviously only applies with a Nav Controller (because of the segues) and it worked when the orientation changed from portrait to Landscape but when I rotate back to portrait, I end up with the landscape VC rotated back to portrait. What am I doing here wrong?
I hope someone can validate/confirm these points and answer my question.
Thanks
KB.
First of all : segues don't apply only with a UINavigationController, any UIViewController is able to perform a segue.
The Apple documentation also say that you'll have to restrict supported orientation of each UIViewController you'll use to handle orientation i.e. you'll have to restrict portrait to portrait only and landscape to landscape only). This could be done following the doc :
For iOS 6 and later : override supportedInterfaceOrientation
For iOS 5 and earlier : override shouldAutoRotateToInterfaceOrientation
Then, in order to use Apple design to manage orientation, you can follow this blog post by Alex Moffat.
It basically describe how to design your view hierarchy using two view controllers displaying the same content (one for Landscape and one for Portrait), embedded in a Navigations Controller. Both view controller should be able to push a hird view controller able to autorotate.
The main idea is to push/pop the Landscape view on/from the navigation stack, and to handle the transition with the third view accordingly.
You should be able to adapt it's code to your own view hierarchy.

UIViewController orientations

I have a UIViewController instance(viewController1) that I want to lock in landscape mode which I am able to do. On tapping on a button I push another UIViewController(viewController2) which supports both orientations. Now, if user changes the orientation of viewController2 to portrait and goes back to viewController1, viewController1 also changes it's orientation to portrait. How can I avoid that?
Thanks in advance.
Add these methods to the view controllers
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
Thats for the first
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES);
}
Thats the second
-(BOOL)shouldAutorotateToInterfaceOrientation:UIInterfaceOrientation)toInterfaceOrientation{
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
return YES;
}
return NO;
}
If those 2 controllers are both implementation of UIViewController both differente classes each other! you can just implement the method shouldAutorotateToInterfaceOrientation, in the first! this should work even when u go back!
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
return YES;
}
return NO;}
You say push so I assume both ViewControllers are in a NavigationController. If so I'll have to disappoint you, what you want isn't possible. Your rotation callbacks are working correctly, they respond to a rotation, you can't force it. What's happening is the correct behavior.
Best solution is to prevent the user from going back when you're in the orientation the previous ViewController doesn't support, hide the back button for example.
A while back I've made my own NavigationController (doesn't inherit from the UIViewController but it can do exactly the same) and I've tried to implement what you're trying to do. Before pushing or popping, if the view of the ViewController that was about to be shown didn't support the current orientation, I transformed the view of that ViewController by 90° and force the orientation of the status bar to the new ViewController's supported orientation.
As soon as the push or pop was complete I'd do a small trick to force the rotation of the device. If you remove the view of the rootViewController from the window and re-add it, the responder chain will be forced to go through all rotation callbacks. When that happened I checked if a ViewController's view was transformed and reset that transformation.
It did work, mostly. But it was messy code and it goes against Apple's current policy that the rootViewController should be responsible of handling the orientation. Also in iOS6 forcing the status bar orientation is guaranteed to work. So I'd really advise against doing this, I've removed this from my own NavigationController too.

Auto-rotation support for view added via presentModalViewController?

It seems that no matter what orientation I am supporting on my views in my app, when I display a view with presentModalViewController, the app snaps into portrait view. How can I support different orientations with a modal view controller?
The controller that you present using presentModalViewController:animated: should support any orientation, typically using this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
With this method, when you present your controller in landscape orientation, it will correctly appear in landscape orientation, too.
I had same problem. I set TRUE AutorotateInterfaceOrientation but at the begin my modalview was always presented Portrait even if it was Landscape. Only if i changed device orientation (in emulator) it's behavior became correct. But at start no!
I don't know why, but all depends from where you present view.
If you present, for example, from viewDidLoad method, then problem occurs. For solving problem, i call presentModalView from a method that's called in viewDidLoad (apparently, it seems same thing). So, i did something like:
- (void) viewDidLoad
{
[super viewDidLoad];
// add this line
[self performSelector:#selector(presentModal) withObject:nil afterDelay:0];
}
-(void) presentModal{
// here present your view
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
This works for me
I just solved this problem for myself by adding code in viewDidAppear. I think the reason it didn't show up in the proper orientation for me is because I had the show modal code in viewDidLoad and at that point, I think the view had just loaded but the orientation was not yet set. So, adding it in viewDidAppear worked for me. You will probably also have luck in viewWillAppear (because by that time, it has to know what the orientation, bounds, sizes, etc are).