In my iPhone app I have a view that I want to show only in portrait mode. When navigating to that view it should be automatically displayed in portrait view. When navigating away, the orientation should change back to what it was, or, if the device orientation has changed, adapt to that. I could find information on forcing an orientation and preventing auto-rotate. I could not find anything on how to change back to the correct orientation after navigating away from that view.
So my idea was to
save the initial orientation (store in currentOrientation)
subscribe to orientation change event to keep track of orientation changes while the content is locked to portrait (update currentOrientation)
when leaving the view, restore the correct orientation using the currentOrientation value.
Edit (code now removed): Apart from it not working it was a dangerous way to go as it made extensive use of unsupported APIs.
Edit:
I believe this question can now be boiled down to the following:
Is there a documented, supported way to force the interface orientation independent of the device orientation? setValue(UIInterfaceOrientation.Portrait.rawValue, forKey: "orientation") has been recommended many times on SO and elsewhere but it does indeed seem to be an unsupported hack.
Is there a documented, supported way to update the interface orientation to the device orientation? That would be needed to "recover" from the forced interface orientation in another view without having to trigger auto rotation by turning the device back and forth.
Supported are supportedInterfaceOrientations() and shouldAutorotate(). But these will only lock the interfaceOrientation after the device has been turned to that position. They do not prevent wrong initial orientation.
There are many questions similar to this one, showing that this problem setting is not uncommon, but so far no satisfactory and complete solution using supported methods.
I had a similar problem except I needed one view controller to only work in Landscape mode and another when it was in portrait. The way I achieved this was making a custom 'root' view controller. Then on the viewWillTransitionToSize method for that controller checking for orientation and non animatedly pushing the correct view controller (so it looks like a rotation to the user). And then in Interface Builder I set the view controller's orientation property explicitly instead of being inferred. You could apply this solution by having only the landscape orientation set on the restricted view controller and then on the portrait rotation doing nothing and disabling auto rotation on the restricted view controller.
Update
I haven't had the time to test any of these but these are just the ideas I used when implementing my solution for a different VC for a different orientation, some combination of the following should hopefully work I can't be a 100% certain about it cause I did this some months ago and don't exactly remember what did and didn't work.
First of all make sure that you have setup the constraints as shown in the screenshot. Mine has iPad full screen and landscape because that's what I was doing change yours to whatever you need (portrait and the size can be inferred).
Now before doing anything else I would first check to see if this solved the problem. I needed the root view controller cause I needed a different VC for portrait and and a different one for landscape. You only need to restrict it so if this works than that's perfect otherwise there are a few other things you can try as mentioned below.
Once that's setup I would first go to the view controller who you want to restrict's class and prevent autorotation using:
override func shouldAutorotate() -> Bool {
return false
}
Now if you do that since you are restricting to portrait I'm guessing you don't really care about upside down so you don't need to do anything additional. If you do want to use the viewWillTransitionToSize method and rotate manually.
If things still don't work you can finally try the root controller way (but I would use this in the last case). Heres a sketch of it:
class VC : UIViewController {
override func viewDidLoad () {
UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name: "UIDeviceOrientationDidChangeNotification", object: nil)
// this gives you access to notifications about rotations
}
func orientationChanged(sender: NSNotification)
{
// Here check the orientation using this:
if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) { // Landscape }
if UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication().statusBarOrientation) { // Portrait }
// Now once only allow the portrait one to go in that conditional part of the view. If you're using a navigation controller push the vc otherwise just use presentViewController:animated:
}
}
I used the different paths for the if statements to push the one I wanted accordingly but you can do just push the portrait one manually for both and hopefully one of the ways above will help you.
Related
I have a app that is locked to portrait in all views except one that is AllButUpsideDown. The approach i am using is to enable Portrait, Landscape Left and Landscape Right in the targets general settings menu. Then have subclasses of UINavigationController and UITabBarController that override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask and returns .Portrait.
Then in my view controller that needs to be able to be rotated I have also overridden func supportedInterfaceOrientations() -> UIInterfaceOrientationMask and returns .AllButUpsideDown. This works fine since this view controller is only presented as a modal i.e aViewController.presentViewController().
All of this work as expected on iOS9 on iOS8 however if i close the rotatable view controller while in landscape the UI will be scaled to landscape altho it will be displayed in portrait.
Anyone know what to do about this? Am I approaching this rotation thing wrong from the start? Any clean fixes? Workarounds? Hacks?
UPDATE
My problem originated from me using a custom transition to present and dismiss the view controller that could rotate. I tried to work around it for some time with bunch of different solutions. The closest I got to a solution was to use a separate UIWindow for my rotatable view controller, that worked except a issue with the carrier bar still being in the wrong orientation, and that was something I did not manage to solve.
The solution(not really a solution) I went with was to only use the custom transition in iOS9+ and on iOS8 use the default present transition.
I had the similar issue when navigation back from VC, that supports landscape to the one that is only portrait. I didn't find a clean workaround. These couple of lines are not recommended to use, but if you are desperate you can force your device orientation when you are about to dismiss.
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
Requirements: just can not automatically rotate a single page, go to horizontal screen outside always come in horizontal screen, vertical screen outside come in to keep the vertical screen.
Action: In the controller of this page and write shouldautorotate method returns NO, find work for all iphone have reached the demand, but the iPad is not working.
Note: Please see, it is a single, specific page without automatic rotation, which is to ensure the program settings page in several directions are checked, and the pull-down device along the shortcut menu to ensure open automatically rotated.
System: iOS9
I had a similar case. Note that I wasn't using navigation controller, it was a simple modal view being presented and this could affect the rotation code.
On the first view of my application I had:
-(BOOL)shouldAutorotate {
return YES;
}
On the view that I wanted to lock on a certain orientation (landscape in my case) I didn't use the shouldAutorotate but used this one:
-(UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
Background: I want to make sure my viewControllers rotate properly when it appears. My viewControllers have excellent codes managing the rotation and orientation when it is visible.
Problem: Given two viewControllers in a NavigationController, viewC1 and viewC2. I did the following:
1.) Set rootViewController to viewC1
2.) Push viewC2 into the NavigationController
3.) Rotate
4.) Pop viewC2
5.) viewC1 is still stucked in the old orientation look (as in the transformation code in willAnimateRotationToInterfaceOrientation was not called) with the new orientation.
What can I do to ensure viewC1 call willAnimateRotationToInterfaceOrientation to reconstruct itself to look correctly in the new rotation?
Additional info:
This is all code (no storyboard/xib). I have shouldAutorotateToInterfaceOrientation return YES on all the views. I use willAnimateRotationToInterfaceOrientation to manage all my rotation.
Oh, and please no hacks. For example, copy the code from rotation then check the rotation mannually and manage it in viewDidAppear.
Think about the name of the method, and what you're trying to achieve.
willAnimateRotationToInterfaceOrientation indicates that the view controlled by the view controller is about to animate to a particular orientation. If your view is in the middle of a navigation stack, then it is not being displayed on screen. To animate something that isn't on screen is costly and ultimately worthless. So, that particular method is out of the question, but the problem that remains is there isn't anything else more appropriate in UIKit. The reason is to rotate something (even if not animated) when it's offscreen is worthless cost. It's the responsibility of the developer to handle a change in orientation when the view appears ("transformation on demand" as you will).
You say you don't want hacks, but the method you've described as a hack is probably the most appropriate thing to do. Having a generic method named something like
-(void) updateLayoutForOrientation:(UIInterfaceOrientation)orientation animated:(BOOL)animated { ... }
isn't a bad idea. This can be the handler for orientation change transformations for the whole view controller.
The places you need to possibly check/handle orientation issues are
-(void) viewWillAppear:(BOOL)animated
-(void) willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation duration: (NSTimeInterval) duration
and in both of these, call updateLayoutForOrientation:animated: to do the work for you.
I'm doing some fancy stuff with rotation, and am returning NO from shouldAutorotateToInterfaceOrientation:. I then manually rotate a UIWebView according to [UIDevice currentDevice].orientationCached. When I hold my finger down on a link, the menu which appears is always in PortraitLeft orientation, no matter how the device is actually oriented, and no matter how the UIWebView is oriented.
It looks to me like the link menu takes its orientation from the main view of the view controller, rather than the UIWebView its associated with, and that the only way to get it to behave correctly is to return YES from -shouldAutorotateToInterfaceOrientation:.
Is this assumption correct? Is there any way to control the orientation of the link-related popup menu, or to force it to take its orientation from the UIWebView which spawns it?
I wouldn't actually set the orientation manually how you're doing it in the UIWebView. Instead, force the orientation of the parent view controller through code.
As an example of a similar situation, I have an app that I've developed that displays a map in a few views. When rotated from portrait to landscape, it layouts the view in a very different manner from the portrait layout with a fancy animation. I like it, but some users don't, so I provide the option to disable map rotation. And I do that by passing in only certain rotation abilities to the parent view. So if they disable landscape, I tell the parent view controller it can only rotate to landscape. Otherwise, it's free to rotate to any orientation except upside down.
Enough with the explanation: here's my relevant code I use to accomplish this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
//Rotate the view if rotation is actually enabled
if ([self.prefs boolForKey:#"SHOULD_ROTATE"]) {
return (UIInterfaceOrientationIsLandscape(toInterfaceOrientation) || UIInterfaceOrientationPortrait == toInterfaceOrientation);
}
//Rotate it to portrait only if rotation is disabled
else if (![self.prefs boolForKey:#"SHOULD_ROTATE"]) {
return (UIInterfaceOrientationPortrait == toInterfaceOrientation);
}
//Otherwise, rotate only to portrait (for all views minus the map views)
else {
return (UIInterfaceOrientationPortrait == toInterfaceOrientation);
}
}
I actually implement this in the UITabBarController so that it applies to all views and view controllers in the app, but it's just as easily implemented only for the UIWebView parent view controller. The gist is that you're returning yes or no based on if the view orientation matches what you want it to. For the web view, you would want to lay out the only allowed rotation orientations to by returning this:
return (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)
Of course, I haven't discussed forcing the view to set the view to a landscape view, only what it does once a user turns it. So the best course of action there is simply to manually do it once the view is initialized:
[[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationLandscapeLeft animated:NO]
You'll have to set it to either Left or Right to start with, but the user can rotate between right and left as they wish if you implement my earlier code as well.
That's a lot of explanation, but I think the combination of these two methods for the parent view controller should allow you to present the view exactly how you wish. Let me know if you have any questions!
By "popup menu", do you mean the Action Sheet with Open … Copy buttons?
If so, then yes, Action Sheet always presents itself according to the top view controller's interfaceOrientation. Because you are rotating the view yourself without its controller updating its interfaceOrientation, the Action Sheet still thinks it is portrait.
How does a UISplitViewController know when it has rotated so that it can trigger the appropriate behavior with managing its views? Is there some way I can manually trigger it myself? I have a split view controller owning a view that is not at the root of my hierarchy, so it is not getting the rotation events that (I think) normally allow it to handle rotation behavior.
You can try to implement UISplitViewController delegate which is:
// Landscape mode
– splitViewController:willShowViewController:invalidatingBarButtonItem:
// Portrait mode
– splitViewController:willShowViewController:invalidatingBarButtonItem:
Since the masterView (left) will show/hide accordingly when the rotation occurs, I found this is more effective compared to handling the orientation changes if each view
I guess UiSplitViewController doesn't autorotate and
iPad: SplitView does not rotate pretty much say that unless the controller's view is the root view, it won't work. Oh apple.
You could sign up for notifications of orientation changing, make sure you have shouldAutorotateToInterfaceOrientation set to YES for the rotations you want to support as well.