Rotation behaviour iOS8 vs iOS9 - ios

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")

Related

iPhoneXR returning to rootview controller after rotation

I currently have a bug that has been reported only on the iPhone XR.
We have a custom camera that forces the rotation into Landscape, and when it is complete, it forces the view back to portrait.
The bug has only been mentioned by users with an iPhone XR. It happens after calling a forced rotation and pop view controller. Rather than returning back to the previous view, it goes back over three view controllers to the root view controller. (edit: From what I can tell the other view controllers aren't called/displayed/loaded at all)
I found this bug happened even when we didn't call..
self.navigationController?.popViewController(animated: true)
So the issue happens specifically with this line..
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
Then after disabling this line, the next screen appears in landscape. BUT if I rotate my phone physically to portrait, it jumps back to the root view controller again.
Notes
I have confirmed on iPhone 6s and older devices this bug doesn't happen.
I cannot test camera features on the emulator which is frustrating.
I have zero code in my app that calls any returns to the root controller.
There is a split view controller at the root of this
Is there some new feature I am unaware of, why would a rotation call on new phones return to the root view controller?
Update:
This is my current lead on the issue.
Popover Nil On Rotation
The problem is that this line
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
is illegal and always was. Your whole notion of forced rotation is wrong. The only legal way to force rotation is a fullscreen presented view controller with a different set of supported orientations.
So I found a solution which I am posting, not to discount matt's response which offers great insight into better practice.
The problem was the Split View Controller when rotating on newer devices makes the popover's nil, hence returning to the root. I found the explanation on this behavior here - Modal disappearing after rotating UISplitViewController
In short I removed the Split View Controller and will be searching for a better alternative to support iPads.

How to force set Landscape Orientation with UINavigationController (Swift 2.0 iOS 9)

I tried to find a solution but so much information which doesn't work. My last try was using the following:
UIApplication.sharedApplication().setStatusBarOrientation(UIInterfaceOrientation.LandscapeRight, animated: false)
This however, was deprecated from iOS 9 and couldn't find any way to force rotate with UINavigationController. My app mainly uses Portrait Orientation and only one view needs to be Landscape. I need to force Landscape on one View and rest to keep as Portrait. Any help would be highly appreciated!
Some of the questions I checked are:
Setting device orientation in Swift iOS
How do I programmatically set device orientation in iOS7?
Why can't I force landscape orientation when use UINavigationController?
If this is something you really want to do, subclass UINavigationController then add this code:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .Landscape
}
Trying to force an orientation imperatively is unwise; it's better to tell iOS what you want (as above) then let it calculate the orientation as best it can.
We had to do this same thing in our app as well. Initially we worked with a hack. But eventually we switched the "Landscape" VC to a modal rather than part of navigation view controller stack. I would suggest you do that. But if you really want to, here is how you do it.
Subclass Navigation VC.
in supportedInterfaceOrientaions check for VC type & return appropriate orientation (landscape for one you want, portrait for rest)
This itself wont autorotate that VC to landscape, so here is the hack.
In viewDidLoad/viewDidAppear of "landscape" VC, push another generic VC object & pop it subsequently
UIViewController *c = [[UIViewController alloc]init];
[self presentViewController:c animated:NO completion:nil];
[self dismissViewControllerAnimated:NO completion:nil];
This used to work in iOS7 & we switched to modal after that. so this might now work in later versions.

shouldAutorotate method not working

I'm a little confused about the auto rotation methods in iOS. I'm using Swift for my app, which contains a tab bar controller and a nav bar controller.
The issue is I want all view controllers to be locked in Portrait mode except for one view controller which shows an image. I want this view controller to be able to be seen in both Portrait or in Landscape orientation based on how the user wants to view the image.
If I turn off the left/right rotation in the deployment info settings and call the shouldAutorotate() - return true method then the view controller with the image stays locked and won't rotate.
If I turn on the left/right rotation in deployment info settings and call the shouldAutorotate() - return false in the view controllers that I want locked then they still auto rotate.
-I feel like this shouldn't be as difficult as it is and can't find a solid answer on this. I'm a little newer to app development so any advice suggestions are appreciated.
Thanks in advance!
Have you set the supportedInterfaceOrientations?
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.AllButUpsideDown
}

Restoring Auto-Rotate after Forcing Orientation in Swift

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.

Can't restrict UIViewController to Portrait only

I'm embedding my app in a UINavigationController, I want most of myViewControllers except one to be Portrait, I've read a lot of questions but could not find a correct answer that works for me.
In my target I'm selecting Device Orientation : Portrait, Landscape Right
I'm adding this to my first ViewController:
-(BOOL)shouldAutorotate{
return NO;
}
-(NSUInteger)supportedInterfaceOrientations{
return (UIInterfaceOrientationPortrait);
}
But when I rotate the device left the ViewController rotates as well.
Why is it rotating?
You can't easily do in iOS 7 what you're describing. A UINavigationController does not consult its children as to what rotations they like; whatever the permitted rotations of the UINavigationController, those are the permitted rotations of the app, regardless of which child happens to be showing at that moment.
The only really legal and built-in way to force rotation is to use a presented ("modal") view controller that takes over the screen. Its rotation settings are consulted because it is now in charge of the screen.

Resources