willRotateToInterfaceOrientation not being called if not visible - ios

I have the following structure (iOS 7 app):
UIWindow -> UITabBarController -> 2 Tabs
Tab1: NavigationController with HomeViewController as root.
Tab2: NavigationController with OtherViewController as root.
If I rotate the iPad while being in Tab1, HomeViewController executes WillRotateToInterfaceOrientation: without problem. But if I'm in Tab2 and rotate the iPad, willRotate: method of HomeViewController in Tab1 doesn't execute, so when I go back to Tab1 the view's layout is in the wrong orientation and messed up.
What's happening? Thanks in advance for your knowledge.

This is the expected behavior on iOS, since HomeViewController was not being shown while the device was rotated.
Checkout the Apple documentation for supporting multiple view controller interface orientations.
Specifically, the section Rotations May Occur When Your View Controller Is Hidden, that reads:
If your view controller’s contents are not onscreen when a rotation occurs, then it does not see the list of rotation messages. For example, consider the following sequence of events:
Your view controller presents another view controller’s contents full screen.
The user rotates the device so that the user interface orientation changes.
Your app dismisses the presented view controller.
In this example, the presenting view controller was not visible when the rotation occurred, so it does not receive any rotation events. Instead, when it reappears, its views are simply resized and positioned using the normal view layout process. If your layout code needs to know the current orientation of the device, it can read the app object’s statusBarOrientation property to determine the current orientation.
So basically you have to prepare your view controller to update itself according to rotations that may have happened while it was not listening to notifications.
The most common way to do this is to place interface-specific code on viewWillAppear:, since this method is called every time your view controller is shown onscreen.

Related

How to detect orientation change while the view controller is locked to portrait mode?

I have an application which has a Tab Bar Controller with two items in it. Each item has its own Navigation Controller. Each part of the application has different behaviors. So I am not able to control the device orientation app-wide. However, in the second part of the app, I mean in the second navigation controller, the behavior should be slightly different.
I have a parent view controller in the second navigation controller. It should be always portrait. So I have made it using a lock method from the answer: https://stackoverflow.com/a/41811798/2152616
Basically, I call lock methods and it works fine. It also pushes to another view controller on some user action. The second view controller does not need to support landscape. So I am able to use the same technique to lock the orientation to portrait. However, when user rotate the device to landscape the I should present another view controller. In the case I lock the orientation, I'm not able to detect the orientation change.
How can I detect the orientation while the view controller supports only portrait mode?

popViewControllerAnimated: From Landscape To Portrait

I'm having trouble popping a UINavigationController (subclass) from a landscape view to a portrait view.
I have a chain set up from the window's rootViewController using shouldAutorotate and supportedInterfaceOrientations down to the individual View Controllers that I present.
So, I have a View Controller that ONLY supports portrait. The next one that is pushed supports landscape as well.
When I push the one that supports landscape and then rotate the device, everything rotates. So far, so good.
Now, when I pop back to the first one, both views rotate BEFORE the animation. So, the second viewController's landscape view (which is really small because of the rotation transform) is pushed away to the right side of the portrait screen.
I want the landscape view to be pushed away to its right (the top or bottom of the portrait view controller), while the portrait view controller is shown in the background.
How can I accomplish this?
I thought I might try to use an animation controller, but the UINavigationController's delegate method, navigationController:animationControllerForOperation: isn't called when popping to a View Controller in a different orientation.
After days of experimentation I figured out what my problem was. Hopefully this will help someone in the future.
I was trying to over-engineer the chain.
I had a custom container with a child tab bar controller which contained multiple navigation controllers that contained view controllers with different orientation requirements.
My main downfall was assuming that even if a modal window was presented, the system would still ask the window's root view controller first. To account for this situation I added a check in the container that looked like this:
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if (self.presentedViewController) { // This check is wrong! Don't do this!
return [self.presentedViewController supportedInterfaceOrientations];
}
// Pass request to child view controller
}
However, this was fundamentally causing my problem in two ways.
First, without this check, UIKit will automatically detect the modal view controller and directly ask for its supportedInterfaceOrientations.
Second, the way UIKit handles the animations involves presenting several internal view controllers that we normally would have no idea are there. By checking for a modal view controller in presentingViewController, the check was catching them and asking them all for their supportedInterfaceOrientations. UIKit isn't meant to behave this way, so I was the one interfering with its operations.
The fix was to implement supportedInterfaceOrientations so that it ONLY concerns the view controller's direct descendants (i.e. the view controllers whose views are descendants in the current view controller's view hierarchy). The direct descendants can further pass the request down from there.
Treat modal windows as a completely separate chain, regardless of the presenter.
In other words, trust UIKit.

navigation controller pushViewController transitioning between landscape and portrait views doesn't work?

I'm trying to get navigation between views that support landscape or portrait orientations working using a navigation controller and pushViewController (and IOS6+ code) and have a problem where after pushing a landscape view controller onto the navigation controller stack, the view controller view correctly is in landscape but the device is still in portrait orientation. (imagine an arrow pointing left to right on a landscape screen, here it points from down to up on a portrait screen so the dimensions are correct - it's just the device should be in landscape mode so the arrow points left to right - if that makes sense)
I have a portraitViewController and landscapeViewController that my different sub modes use.
Using the iOS 6 method, The two classes overload
-(BOOL)shouldAutorotate;
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
-(NSUInteger) supportedInterfaceOrientations;
appropriately.
I also have my own derived navigationcontroller that overrrides:
-(BOOL)shouldAutorotate;
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
-(NSUInteger) supportedInterfaceOrientations;
with the functions returning the same functions of the top view controller (so we're not bound to portrait mode by the top level view controller).
My first problem is that the device doesn't do an orientation refresh when you use pushViewController onto the navigation controller. This is well documented and has been solved up to now by using dismissViewController and presentViewController which forces a device orientation refresh which in turn queries the new view controller about what orientations it supports and switches the device appropriately.
This works but loses transitioning between view controllers (i.e with the new view sliding in from the right of the screen) as the previous view is removed from screen before the next is added and the newly presented view is applied to the entire screen directly.
Because of this, I've been trying to get things working using pushViewController rather than present / dismissViewController. pushViewController works fine when I navigate from portrait to portrait view - the new view slides in nicely - it's when I switch from a portrait to landscape view controller than things break.
Just pushViewController doesn't cause the device to refresh it's orientation and query what the new view would like to use. I've added calls to present / dismissModalViewController before my call to pushViewcontroller to force an orientation check with the new view controller.
* the problem *
This doesn't work however because the view is now in landscape mode but the device is still in portrait mode so the view is at 90 degs to what it should be.
I've tried fixing this by setting the status bar orientation to match the view controller landscape orientation but this causes massive problems when the user manually rotates the device from then on as they aren't in sync. Also in iOS 6, you can't do that if your view controller returns YES from shouldAutoRotate function (calls to setStausBarOrientation are ignored if you control orientation yourself in your view controller) so it took a large hack to do it and it just doesn't feel like the right solution.
To summarise, how can I use a navigation controller & pushViewController (using iOS6) and switch between landscape and portrait views properly? At the moment, I can't get the device to update along with the view so the interface is in landscape but the device is still in portrait. If I try using setStatusBarOrientation to force the device to landscape, things start to go wrong as soon as I manually rotate the screen and it requires such a hack it just can't be the right answer.
thanks!
:-)

Keep one view orientation when rotating

I have the app with two view controllers.
The first view controller contains two subview: one would keep it's orientation; second - would be rotated (like camera layer and controls in iOS Camera app).
The second – should support all orientations.
I've found a solution: to keep first view controller in Portrait mode and rotate the subview manually, by handling UIDeviceOrientationDidChangeNotification.
The problem is in iOS 6.0.
I've tried to add category for UINavigationController, but it seems like rotation rules are global.
A sample code is attached
https://dl.dropbox.com/u/2167984/temporary/rotationSample.zip
I've resolved this issue: just moved first view controller out of navigation controller and show the second view controller as modal.

view controller hierarchy and device orientation changes

When orientation changes occur and view controllers are subsequently notified of changes, does the entire of the root view and all its subviews receive these notifications? I have created a root view and a subview to that root view. Does the subview's controller (and any controllers of subviews in this hierarchy) receive all the rotation handling resulting from device orientation changes?
I ask because nested in this hierarchy is a UISplitViewController's view, and I suspect it is not receiving notification of device orientation changes. i.e. I essentially have something like A -> B -> C in my view hierarchy where C is a UISplitViewController's view.
When orientation changes occur only the first view of the window will rotate together with its entire hierarchy.
In terms of viewControllers, the first view's viewController will get the orientation change events (and viewWill/DidAppear events) and all the viewControllers spawn by it. By spawn I mean a viewcontroller displayed using either as a tab of a uitabbarViewController, or added to a navigation controller, or displayed modally.
In other words if somewhere in your hierarchy you added a viewcontroller's view manually to the window or another view, that viewcontroller will not get any events.
See this: why does viewDidAppear not get triggered?
same thing happens for orientations changes
So there are 2 possible causes:
your uisplitviewController's view or any of its parent is added manually to the view hierarchy instead of pushing the viewcontroller (the view gets rotated but does not receive events)
you added the view as a second child of the window (the view doesn't get rotated at all)

Resources