iPad modal view controller rotates interface on dismiss - ios

I have a problem with an app I am working on that goes like this:
The app's window has a rootViewController which is set to a custom class (MenuViewController) of UIViewController. This view controller has a rootViewController property of it's own. Whenever set this happens (really short version of the code):
- (void)setRootViewController:(UIViewControlelr *)rootViewController
{
...
_rootViewController = rootViewController;
if (self.rootViewController) {
[self addChildViewController:rootViewController];
[self.view addSubview:rootViewController.view];
}
...
}
Now this MenuViewController can show a modal view controller on top of it's rootViewController.
I do that like this:
[self.rootViewController presentModalViewController:viewController animated:YES completition:nil];
Everything looks to be ok until here. Now on iPad whenever I call [self.presentingViewController dismissViewControllerAnimated:YES completion:nil] from my modal view controller the interface beneath it rotates to the same orientation (that is UIInterfaceOrientationLandscapeLeft) regardless of what the initial orientation was when the view controller was presented.
So to conclude, my view's hierarchy is this:
Window
|
- Menu View Controller
|
- Root View Controller
|
- Modal view controller
Does anyone know how I can fix this? It doesn't happen on the iPhone.

It sounds to me as though on the iPad your MenuViewController's rootViewController supports multiple interface orientations, whereas on the iPhone it does not. This is speculation, as you have not said anything about this.
If this is the case, and the rootViewController does indeed support multiple interface orientations, the fix would be to override - supportedInterfaceOrientations and return portrait, which seems to be what you are suggesting you would want.

Related

UIModalPresentationCustom messes up orientation settings

When presenting a modal with UIModalPresentationCustom, it ignores the orientation methods, and displays / rotates to whatever the presenting VC is configured to.
Example:
Presenting VC supports Landscape and Portrait.
Presented VC supports Portrait only (via preferredInterfaceOrientationForPresentation and supportedInterfaceOrientations.
When presenting it in landscape without UIModalPresentationCustom, it rotates the view back to portrait, then presents the VC accordingly. Unfortunately, because I need the presenting VC to stay visible below, I am forced to use UIModalPresentationCustom. And when that happens, the presenting VC is forced into landscape mode, creating a messed up UI and generating constraint issues. And even when presenting in portrait, it becomes allowed to rotate into landscape, ignoring that shouldAutorotate returns NO.
PS: I found a workaround on iOS 7 by adding this method to my App Delegate, but it doesn't fix it on iOS 8.
#implementation UIViewController (customModalFix)
- (BOOL)shouldAutorotate
{
if ([self.presentedViewController isKindOfClass:[IntroViewController class]]) {
return [self.presentedViewController shouldAutorotate];
}
return YES;
}
#end
EDIT: Implementing supportedInterfaceOrientations on the presenting VC doesn't help at all, since it is only called when the view is loaded, not when a VC is about to be presented over it. Still haven't found a solution to this problem.
Maybe I'm late. The point is, when using UIModalPresentationCustom, the presenting VC will not disappear, and the presented VC is not considered to be presented full-screen (even if it does take up the full screen). Thus, it's the presenting VC that is consulted for the supported interface orientations. So the solution can be like:
- (NSUInteger)supportedInterfaceOrientations
{
if (self.presentedViewController) {
return [self.presentedViewController supportedInterfaceOrientations];
}
return [super supportedInterfaceOrientations];
}
If you only use UIModalPresentationCustom to keep the presenting VC visible below, say you need a clear colored VC, my answer here may work for you too:
https://stackoverflow.com/a/29167837/46940801

iOS8 - Presenting a modal view removes sub view

I am updating our app to be compiled with xcode6/iOS8.
One issue I am running into is that when a modal view is presented. the underlying subview is removed. It is completely blacked out.. until the modal view is dismissed.. then it re-appears.
Has anyone run into this with iOS8? The same code has worked since iOS4.
Code:
PigDetailViewController *pigDetailViewController = [[PigDetailViewController alloc] initWithNibName:#"PigDetailViewController" bundle:nil];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
self.navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:pigDetailViewController animated:YES completion:nil];
In iOS 8 they've added a new presentation style that behaves like UIModalPresentationCurrentContext in the circumstance you've described, it's UIModalPresentationOverCurrentContext. The catch here is that unlike with UIModalPresentationCurrentContext, you want to set the view controller to be PRESENTED with this presentation style, like so:
PigDetailViewController *pigDetailViewController = [[PigDetailViewController alloc] initWithNibName:#"PigDetailViewController" bundle:nil];
pigDetailViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
self.navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:pigDetailViewController animated:YES completion:nil];
Note that to support iOS 7 and below you'll likely need to check the OS version and decide how to present the view controller based on that.
edit: I'd like to add one more note to this. In iOS7 with UIModalPresentationCurrentContext, when the presented VC was dismissed, the underlying VC had its viewDidAppear method called. In iOS8 with UIModalPresentationOverCurrentContext, I've found the underlying VC does not have its viewDidAppear method called when the VC presented over top of it is dismissed.
Adding a point to BrennanR's answer.. even viewWillAppear doesn't call when the VC presented over top of it is dismissed.
I think you are misunderstanding how a modal view controller works.
When you present a view controller modally it will control the entire screen. It has an opaque background (normally black) and then draws its view on top of that.
So, if you set the view.backgroundColor to yellow (for example) it will have a yellow background. If you set it to clear then it will show through to the black background.
What I think you want is for the other view to "show through" so it looks like the modal view is sitting on top of it.
The best way I have found of doing this is to use this method...
// in the view controller that is presenting the modal VC
modalVC = // the modal VC that you will be presenting
UIView *snapshotView = [self.view snapshotViewAfterScreenUpdates:NO];
[modalVC.view insertSubView:snapshotView atIndex:0];
// present the modal VC
This will take a "screenshot" of the current view hierarchy and then place that snapshot underneath everything in the modal VC.
That way your black screen will be replaced by a screenshot of the previous view controller.

iOS7 SDK force Landscape for viewController

On iOS6 I had a method to make one view controller in my navigation-style app auto rotate to landscape when I pushed it. (Basically present a bogus view controller and dismiss it in viewWillAppear).
UIViewController *mVC = [[UIViewController alloc] init];
[self presentModalViewController:mVC animated:NO];
if (![mVC isBeingDismissed])
[self dismissModalViewControllerAnimated:NO];
With the latest SDK this no longer works. Does anyone have another way to auto rotate?
Turns out the solution is simple, just pass YES to dismissModalViewControllerAnimated
UIViewController *mVC = [[UIViewController alloc] init];
[self presentModalViewController:mVC animated:NO];
if (![mVC isBeingDismissed])
[self dismissModalViewControllerAnimated:YES]; //Fix here
From Developer site
"When a view controller is presented over the root view controller, the system behavior changes in two ways. First, the presented view controller is used instead of the root view controller when determining whether an orientation is supported. Second, the presented view controller can also provide a preferred orientation. If the view controller is presented full screen, the user interface is presented in the preferred orientation. The user is expected to see that the orientation is different from the device orientation and rotate the device. A preferred orientation is most often used when the content must be presented in the new orientation."
I think here you can use the preferred orientation method here.

Support landscape for only one view in UINavigationController

I have a navigation controller which have a few view controllers. I need to support all orientations for all view controllers except one special view controller which only supports landscape. This special view controller appears in the middle of the navigation stack. I have done quite a lot of research but couldn't find any good solution. Here are the links that I have read and tried.
http://www.iphonedevsdk.com/forum/iphone-sdk-development/3219-force-landscape-mode-one-view.html#post60435
How to rotate screen to landscape?
How to autorotate from portrait to landscape mode?
iPhone - allow landscape orientation on just one viewcontroller
http://goodliffe.blogspot.com/2009/12/iphone-forcing-uiview-to-reorientate.html
Next I am going to try to replace navigation controller with presentModalViewController in order to display the special view controller. Then I am going to create a new navigation view controller inside the special view controller to push the subsequent view controllers.
If anyone has a better idea, please let me know. Really appreciated!
UPDATE: I have successfully use the method I described above: replace pushViewController with presentModalViewController and create a new navigation controller.
Every view controller pushed onto the navigation controllers stack have to support the same orientations. This means that it is not possible to have some view controllers only supporting portrait and others only supporting landscape. In other words all view controllers on the same navigation controller stack should return the same in the delegate:
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
But there is a simple solution to this! Here is an example for going from portrait to landscape. Here is the steps to do it and below is code to support it.
Create a ‘fake’ view controller that will be root in a sub navigation controller. This view controller should support landscape.
Create a new instance of a UINavigationController, add an instance of the ‘fake’ view controller as root and an instance of your landscape view controller as second view controller
Present the UINavigationController instance as modal from the parent view controller
First, create a new view controller (FakeRootViewController) with this code:
#interface FakeRootViewController : UIViewController
#property (strong, nonatomic) UINavigationController* parentNavigationController;
#end
#implementation FaceRootViewController
#synthesize parentNavigationController;
// viewWillAppear is called when we touch the back button on the navigation bar
(void)viewWillAppear:(BOOL)animated {
// Remove our self from modal view though the parent view controller
[parentNavigationController dismissModalViewControllerAnimated:YES];
}
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationIsLandscape(interfaceOrientation));
}
Here is the code to present the view controller that you wish to show in landscape mode:
FakeRootViewController* fakeRootViewController = [[FakeRootViewController alloc] init];[fakeRootViewController.navigationItem setBackBarButtonItem:backButton]; // Set back button
// The parent navigation controller is the one containing the view controllers in portrait mode.
fakeRootViewController.parentNavigationController = parentNavigationController;
UINavigationController* subNavigationController = // Initialize this the same way you have initialized your parent navigation controller.
UIViewController* landscapeViewController = // Initialize the landscape view controller
[subNavigationController setViewControllers:
[NSArray arrayWithObjects:fakeRootViewController,
landscapeViewController, nil] animated:NO];
[_navigationController presentModalViewController:subNavigationController animated:YES];
Remember that the landscapeViewController should also have this implementation:
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationIsLandscape(interfaceOrientation));
}
There's a private API to force an orientation change. Put in your pushed view controller's -viewWillAppear::
if ([UIDevice instancesRespondToSelector:#selector(setOrientation:)]) {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
}
To suppress the compiler warning, add this to the .m file of your view controller:
#interface UIDevice()
- (void)setOrientation:(UIDeviceOrientation)orientation; // private API to let POIEntryVC be pushed over landscape route view
#end
As always, there's a risk of being rejected and a risk of breaking in future OS versions when using private APIs. Do at your own risk!
Generally, presenting a modal view controller is the better solution in most cases.
You can make actions:
Change Your code with accordance of schellsan suggestion, next -
Try to add currentViewController(which will push to navigation viewController) as property to appDelegate. When You attempt to push view controller, set it to current view controller before this. Next - make a subclass of rootViewController in navigation controller. In this subclass owerload method
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return [appDelegate.currentViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
It should works if You not using a navigation bar and pushes new controller after popping an old
It should be as simple as implementing
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
in each UIViewController pushed into your UINavigationController. In the case that one UIViewController's view shouldn't rotate, return NO for that specific orientation in that specific UIViewController.
There's a gotcha here though, if your UINavigationController implements
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
it will block its viewControllers from receiving that method. In that case, you should forward the message to the topViewController using
[[self topViewController] shouldAutorotateToInterfaceOrientation:interfaceOrientation];
You could try this code in your UINavigationController to call the current visible view's shouldAutorotateToInterfaceOrientation. In my case I have the UINavigationController in a UITabBarController but you could probably adapt it to other cases.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ([appDelegate.tabBarController.selectedViewController respondsToSelector:#selector(topViewController)])
{
UINavigationController *nc = (UINavigationController*)appDelegate.tabBarController.selectedViewController;
return [nc.topViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

iPad Modal View rotates parentViewController View

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

Resources