Lock orientation of a UIViewController on iOS 8 [duplicate] - ios

I have this app I am working on and I need ALL my view controllers but one to be in portrait.
The single one view controller that is special I need it to be able to rotate to whatever orientation the phone is in.
To do that I present it modally (not embedded in a NavigationController)
So (for example) my structure is like this:
window - Portrait
root view controller (UINavigationController - Portrait)
home view controller (UIViewController - Portrait)
details view controller (UIViewController - Portrait)
.
.
.
modal view controller (UIVIewController - All)
Now when ever I dismiss my modal view controller in a landscape position my parent view controller is ALSO rotated even though it doesn't support that orientation.
All UIViewControllers and UINavigaionControllers in the app inherit from the same general classes which have these methods implemented:
override func supportedInterfaceOrientations() -> Int
{
return Int(UIInterfaceOrientationMask.Portrait.toRaw())
}
My modal view controller overrides this method once again and it looks like this:
override func supportedInterfaceOrientations() -> Int
{
return Int(UIInterfaceOrientationMask.All.toRaw())
}
Update 1
It looks like this is happening only on iOS8 Beta.
Does someone know if there is something that changed regarding view controller's rotation or is this just a bug in the beta?

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if ([self.window.rootViewController.presentedViewController isKindOfClass: [SecondViewController class]])
{
SecondViewController *secondController = (SecondViewController *) self.window.rootViewController.presentedViewController;
if (secondController.isPresented)
return UIInterfaceOrientationMaskAll;
else return UIInterfaceOrientationMaskPortrait;
}
else return UIInterfaceOrientationMaskPortrait;
}
And for Swift
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow) -> Int {
if self.window?.rootViewController?.presentedViewController? is SecondViewController {
let secondController = self.window!.rootViewController.presentedViewController as SecondViewController
if secondController.isPresented {
return Int(UIInterfaceOrientationMask.All.toRaw());
} else {
return Int(UIInterfaceOrientationMask.Portrait.toRaw());
}
} else {
return Int(UIInterfaceOrientationMask.Portrait.toRaw());
}
}
For more details check this link

I'm having the same issue with an app and after days of experimentation I came up with a solution which is not very nice but it works for now. I'm using the delegate method application:supportedInterfaceOrientationsForWindow: within the appdelegate.
I created a test project and put it here on github (including a GIF which shows the result...)
// note: it's not in swift but I hope it helps anyways

After much experimentation, I am convinced that this is a "feature" of iOS 8.
If you think about it, this makes perfect sense, because it has been coming for a long time.
In, say iOS 4, it was possible to force app rotation when changing view controllers in a tab bar controller and a navigation controller, as well as when presenting/dismissing a controller.
Then in iOS 6 it became impossible to force app rotation except when presenting/dismissing a view controller (as I explained in many answers, such as this one).
Now, in iOS 8, I conjecture that it will be impossible to force app rotation at all (except at launch). It can prefer a certain orientation, so that once it is in that orientation it will stay there, but it cannot force the app to go into that orientation.
Instead, your view controller is expected to "adapt". There are several WWDC 2014 videos concentrating on "adaptation", and now I'm starting to understand that this is one reason why this is so important.
EDIT: In seed 4, it looks like this feature (forcing rotation on presentation and dismissal) is returning!

We have an app deployed that has a landscape controller which presents a portrait-only view controller. Was reviewed by Apple on iOS 8. We are only overriding supportedInterfaceOrientations.
It's worth noting that we found lots of differences between betas 3,4,and 5. In the end we had to wait for the GM before making a concerted effort to update our app for iOS 8.
You need to be very careful in iOS 8 to make sure that you don't do this:
[self dismissViewControllerAnimated:YES completion:nil]
[self presentViewController:vc animated:YES completion:nil]
If the outgoing vc is portrait, and the incoming one is landscape, some view frames can end up very messed up. Present the incoming vc in the completion block of the dismiss call instead.
[self dismissViewControllerAnimated:YES completion:^{
[self presentViewController:vc animated:YES completion:nil]
}];

This is actually easier and can be done without any additional properties (here's an example with AVPlayerViewController):
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
if ([self.window.rootViewController.presentedViewController isKindOfClass: [AVPlayerViewController class]])
return self.window.rootViewController.presentedViewController.isBeingDismissed ?
UIInterfaceOrientationMaskPortrait : UIInterfaceOrientationMaskAll;
else
return UIInterfaceOrientationMaskPortrait;
} else {
return UIInterfaceOrientationMaskAll;
}
}

Awesome question and awesome answer provided by #ZaEeM ZaFaR! Combining his answer with this led me to a great and more generic solution.
The drawback of the first answer is that you have to manage the variable isPresented in every view controller that allows rotations. Furthermore, you have to expand the check and cast in supportedInterfaceOrientationsForWindow for every vc that allows rotation.
The drawback of the second answer is that it doesn't work; it also rotates the presenting vc when dismissing the presented vc.
This solution allows rotation in all vc where you put canRotate(){} and doesn't rotate the presenting vc.
Swift 3:
In AppDelegate.swift:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
if (rootViewController.responds(to: Selector(("canRotate")))) {
// Unlock landscape view orientations for this view controller if it is not currently being dismissed
if !rootViewController.isBeingDismissed{
return .allButUpsideDown
}
}
}
// Only allow portrait (standard behaviour)
return .portrait
}
private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
if (rootViewController == nil) {
return nil
}
if (rootViewController.isKind(of: UITabBarController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
} else if (rootViewController.isKind(of: UINavigationController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
} else if (rootViewController.presentedViewController != nil) {
return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController
}
In each view controller where rotation should be allowed:
func canRotate(){}

In the root view controller, try adding:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
Worked for me.

Swift 3.0 OR Above,
Just check "isBeingDismissed" property of presented view controller.
Below is sample code, This is will rotate presenting view controller to portrait mode immediately after presented view controller is dismissed.
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController)
{
if rootViewController.canRotateVC == true
{
if baseVC.isBeingDismissed == false
{
return .allButUpsideDown
}
}
}
return .portrait}
you can get topController by below code:
private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController?{
if (rootViewController == nil) { return nil }if (rootViewController.isKind(of: (UITabBarController).self))
{
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
}
else if (rootViewController.isKind(of:(UINavigationController).self))
{
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
}
else if (rootViewController.presentedViewController != nil)
{
return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController }

I had the same issue, finally found a solution to open the modal view controller in another UIWindow, and it worked smoothly.
Stack -
iOS8 - prevent rotation on presenting viewController
For code:
https://github.com/OrenRosen/ModalInWindow

Related

Prevent UIViewController from rotating when modal is rotating

My App is basically a portrait only app. So nothing is rotating. But not there is ONE exception. The user can add photos and when viewing those photos full-screen, this ViewController SHOULD be allowed to rotate.
So I thought that if my ViewController that is presenting has supportedInterfaceOrientations return .portrait and also shouldAutorotate return false, that this should be enough, to prevent that one from rotation?!?
Turns out, that when I rotate while having the full-screen image presented, the one underneath is rotated as well.
To summarize:
RootViewController should NEVER rotate
PresentedViewController can rotate, but his rotation should no rotate the RootViewController
Is there a way to achieve that?
Probably too late, but in case if somebody will be faced with the same issue, i would provide my solution.
Actually suppress rotation of underlying window is possible if set modalPresentationStyle = .fullScreen for presented controller, then if you take a look of "View UI Hierarchy" then you can notice that controller which represents controller in fullscreen will be removed from controllers hierarchy. But at the same time modalPresentationStyle = .overFullScreen keep everything as is, what causing rotating underlying controller even it set by default supported orientation to portrait, i.e. UIWindow who manages and routes system events over hierarchy respect settings of the toppest controller in case of modalPresentationStyle = .overFullScreen. So according to the facts, and if it is necessary to have e.g. custom presentation, i would suggest to use additional UIWindow which will be responsible for the presenting controller in fullscreen.
i have implemented test project for the solution: here
You can give an exception like in AppDelegate:
//auto rotate
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
//landscape for perticular view controller
let navigationController = window?.rootViewController as? UINavigationController
if let activeController = navigationController?.visibleViewController {
if activeController.isKind(of: VGVideoVC.self) {//Pass your VC here
// print("I have found my controller!")
return UIInterfaceOrientationMask.all;
}else{
return UIInterfaceOrientationMask.portrait;
}
}else{
return UIInterfaceOrientationMask.portrait;
}
}
And in the Rest of the VC where you want it to be forcefully portrait, you can use like this:
//MARK:- Screen Orientation
override var supportedInterfaceOrientations: UIInterfaceOrientationMask{
return .portrait
}
override var shouldAutorotate: Bool{
return true
}
Hope this helps.
I would say disable the orientation change for the complete app and listen to device orientation change in Photos view controller and update the UI of photosVC on device orientation change.
Something like this:
NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: Notification.Name("UIDeviceOrientationDidChangeNotification"), object: nil)
#objc func orientationChanged() {
if(UIDeviceOrientationIsLandscape(UIDevice.current.orientation)){
print("landscape")
}
if(UIDeviceOrientationIsPortrait(UIDevice.current.orientation)){
print("Portrait")
}
}
Be careful with the upside down and other orientations which you don't need.
Try this code below. I followed This tutorial and it works for me. What's going on is:
Step 1. Assuming inside General your Device Orientation is set to Portrait only:
Step 2. The code below that you add inside AppDelegate loops through the navigation controllers and then looks inside their top view controllers. If any of those vcs have a function with the name canRotate then that specific vc will change the device orientation from Step 1. by returning: return .allButUpsideDown
Add these 2 functions to the bottom of your AppDelegate:
// add this first function
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
// if the navigationController's root vc has a function inside of it named canRotate
if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
if (rootViewController.responds(to: Selector(("canRotate")))) {
// Unlock landscape view orientations for this view controller
return .allButUpsideDown;
}
}
// Only allow portrait (standard behaviour). vcs that don't contain a function with the name "canRotate" can't rotate and stay in portrait only
return .portrait;
}
// add this second function
// loop through tabBarController or any navigationControllers
private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
if (rootViewController == nil) { return nil }
if (rootViewController.isKind(of: UITabBarController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
} else if (rootViewController.isKind(of: UINavigationController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
} else if (rootViewController.presentedViewController != nil) {
return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController
}
Step 3. Inside the modal vc you should add a function named: #objc func canRotate(){}. You don't have to call it anywhere or add anything inside it's curly braces. The code from Step 2 is looking for this function with the name canRotate. If the other vcs don't contain a function with that name then they can't rotate.
Inside the modal viewController that you want to rotate add the canRotate() function anywhere outside of viewDidLoad and inside viewWillDisappear add the code to set everything back to your regular Portrait only :
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func canRotate(){}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// add this so once leaving this vc everything will go back to Portrait only
if (self.isMovingFromParentViewController) {
UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation")
}
}

Lock only one ViewController's orientation to landscape, and the remaining to portrait

I want to lock orientation in all ViewControllers to portrait, except on one of them, when it is pushed always to be in landscapeRight.
I've tried many solutions, using extensions for UINavigationController, overriding supportedInterfaceOrientations and shouldAutorotate but no luck.
I've found the solution, which for now it is working.
On every view controller you should put this code for supporting only the desired rotation (landscapeRight in the example):
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.landscapeRight
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeRight
}
On the other implement the same methods but with portrait orientation.
Create an extension for UINavigationController
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return (self.topViewController?.supportedInterfaceOrientations)!
}
open override var shouldAutorotate: Bool {
return true
}
Note: Also in Project's target enable all desired supported
orientations.
The only one controller that I wanted to show in landscape mode i presented modally.
You will run into confusion if you use the navigation controller to present views. For example, we usually leverage the navigation controller to handle adding views by calling navigation controller instance functions like performSegueWithIdentifier:sender: or pushViewController:animated:.
Pushing is not going to work like you want it to.
And segues will probably not work either, especially if in IB you created the segue off the navigation controller!
Anyway, if you somehow succeeded in overriding the "parent" navigation controller's orientation, you risk running into edge cases down the line and you are probably using something sketchy that may lose support with any update.
That said, the workaround I have found is:
Step 1
Use presentViewController:animated:completion: to present landscapeRight view controller:
// assuming you're using IB and Main bundle
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let landscapeViewController = storyboard.instantiateViewController(withIdentifier: "LandscapeViewControllerStoryboardID")
self.present(landscapeViewController, animated: true, completion: {
print("landscapeViewController presented")
})
Step 2
Adding the following override to your landscapeViewController actually works now:
/* override the preferredInterfaceOrientationForPresentation for a view controller
that is intended to be presented full screen in a specific orientation.
https://developer.apple.com/reference/uikit/uiviewcontroller */
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeRight
}
Step 3 (Optional)
With all this working, there is still some weirdness when you dismiss your landscapeViewController: It is in landscape orientation now as well.
This is because the navigation controller is playing nice. In other words, now it's the navigation controller's turn to try to force an orientation change, force it back to the way it was.
In order to do this, you need to store the orientation of the device before you present the landscapeViewController. In the view controller from which you present the landscapeViewController, I stored it in an instance variable:
let ratio = self.view.bounds.width / self.view.bounds.height
if (ratio > 1) { // horizontal/landscape
self.currentOrientationIsPortrait = false // read by navigation controller when dismissing full screen view player
} else {
self.currentOrientationIsPortrait = true // read by navigation controller when dismissing full screen view player
}
Then, I test that view controller instance's value in my navigationController class' preferred orientation override. Let's say the view controller from which I present the landscapeViewController is called presentingViewController:
// execution order: during visibileViewController's dismissal
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
get {
if visibleViewController != nil {
// check if landscapeViewController is being dismissed
if (visibleViewController as? LandscapeViewController) != nil {
// check if the top view controller is one that tracks its orientation (currently only presentingViewController)
if let presentingViewController = self.viewControllers.last as? PresentingViewController {
if presentingViewController.currentOrientationIsPortrait {
return .portrait // TODO should we not support portraitUpsideDown?
}
}
}
// otherwise, return whatever
return visibleViewController!.preferredInterfaceOrientationForPresentation
}
// otherwise, return whatever
return super.preferredInterfaceOrientationForPresentation
}
}
And, bingo. (Hopefully)
EDIT
Also don't forget to put this code in others ViewControllers:
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}

Not able to lock orientation for 1 VC

I am trying to get device rotation right.
I am testing on iPad 8.x/9.x simulator
I have 4 VCs
VC1 - Both Portrait and Landscape
VC2 - Both Portrait and Landscape
VC3 - Only Portrait
VC4 - Both Portrait and Landscape
Goal: to have VC3 display PortraitView at all times (same as if app orientation was fixed to portrait).
I tried
#implementation RotationAwareNavigationController
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
UIViewController *top = self.topViewController;
return top.supportedInterfaceOrientations;
}
-(BOOL)shouldAutorotate {
UIViewController *top = self.topViewController;
return [top shouldAutorotate];
}
#end
In VC which is portrait
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
But it does not work meaning view not displayed in Portrait dimensions Am I missing something?
I am sure it can be done as when I use ImagePickerController provided my iOS, it is fixed to Portrait. I just dont know how to do it.
To support differing orientations in different view controllers, you will need to do a few things. First, you need to check all the checkboxes for orientations you want to support in your target's General settings tab.
Second, anytime you call presentViewController or dismissViewController in your app, the UIApplicationDelegate method application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask will be called. You can use this method to restrict (or allow) specific orientations each time a new view controller is presented or dismissed. Unfortunately, it isn't as simple as just returning a UIInterfaceOrientationMask here. You will need to find the view controller that is going to be showing on screen and return the orientations that it supports. Here is an example of this:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
guard let window = window, let rootViewController = window.rootViewController else {
return UIDevice.currentDevice().userInterfaceIdiom == .Pad ? .All : .AllButUpsideDown // iOS defaults
}
// Let the view controller being shown decide what orientation it wants to support. This method will be called anytime a new view controller is presented on screen.
return findVisibleViewController(rootViewController: rootViewController).supportedInterfaceOrientations()
}
/// Searches the view hierarchy recursively and finds the view controller that is currently showing.
private func findVisibleViewController(rootViewController rootViewController: UIViewController) -> UIViewController {
if let presentedViewController = rootViewController.presentedViewController {
// Search for a modal view first.
return self.findVisibleViewController(rootViewController: presentedViewController)
} else if
let navigationController = rootViewController as? UINavigationController,
let visibleViewController = navigationController.visibleViewController {
// Then search navigation controller's views to find its visible controller.
return self.findVisibleViewController(rootViewController: visibleViewController)
} else if let splitViewController = rootViewController as? UISplitViewController {
// Then try the split view controller. This will be the true root view controller. Use the master here since the detail just shows web views.
return self.findVisibleViewController(rootViewController: splitViewController.viewControllers[0])
} else {
// Guess we found the visible view controller, because none of the other conditions were met.
return rootViewController
}
}
findVisibleViewController(_:) is an example from one of my projects and is tailored to the exact view controller hierarchy in my app. You will need to edit this for your own app in a way that makes sense for your hierarchy.
Third, you will need to implement supportedInterfaceOrientations() for most, if not all, of your view controllers.
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return // portrait, landscape, or combinations of those.
}
Finally, this only handles situations where you presented or dismissed something modally. For show (push) segues, the orientation of the navigation controller will be used for every new view controller pushed onto the stack. If you need more fine grained control here, you will need to force the orientation to happen. Here is an example of that:
// Some other view had the screen in landscape, so force the view to return to portrait
UIDevice.currentDevice().setValue(UIInterfaceOrientation.Portrait.rawValue, forKey: "orientation")

Play video fullscreen in landscape mode, when my entire application is in lock in portrait mode

I want to play video in landscape mode in fullscreen.
And my application is lock in portrait mode.
How to implement this. Please help me.
Thanks in advance
Simplest solution in swift 3
Add this to your app delegate:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if window == self.window {
return .portrait
} else {
return .allButUpsideDown
}
}
I've done this in on of my app. To do this you need to check that is your viewcontroller that where you want to play a video.
The first thing you have to do is check Device Orientation to Portrait,Landscape left, Landscape right in your project target
In your AppDelegate do the below code
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
UIStoryboard *mainStoryboard;
if (IS_IPHONE) {
mainStoryboard= [UIStoryboard storyboardWithName:#"Main" bundle: nil];
}
else{
mainStoryboard= [UIStoryboard storyboardWithName:#"Main_iPad" bundle: nil];
}
ViewControllerThatPlaysVideo *currentViewController= ViewControllerThatPlaysVideo*)[mainStoryboard instantiateViewControllerWithIdentifier:#"postDetailView"];
if(navigationController.visibleViewController isKindOfClass: [ViewControllerThatPlaysVideo class]]){
if([currentViewController playerState])
return UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskPortrait;
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskPortrait;
}
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:value forKey:#"orientation"];
Just call it in - viewDidAppear: of the presented view controller.
Another way :
First, you have to understand that in order to open even just one view out of over 100 in landscape, your app should allow both landscape and portrait interface orientations. It is the case by default, but you can check it in your target's settings, General tab, Deployment Info section
Then, because you allowed both landscape and portrait for the entire app, you will have to tell every portrait-only UIViewController that it should not autorotate, adding this method's implementation:-
- (BOOL)shouldAutorotate {
return NO;
}
Finally, for your specific landscape-only controller, and because you said you are presenting it modally, you can just implement these methods:-
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationLandscapeLeft; // or Right of course
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
Hope this will help :)
After staring at this page for almost an entire day, I finally figured out a working time saving solution for all,
Go to the project navigator, select your project and in General select just potrait in device orientation.
Also make sure that you're using a modal segue to show the landscapeController.
Now add this in your controller where in you want the landscape mode.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeRight
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeRight
}
And that's it folks.

Override App Orientation Setting

I have an app where I set the orientation to be Portrait only in it's target settings:
In one particular view controller I'd like to override this setting so auto layout will update views when a device is rotated. I've tried these methods with no success:
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.AllButUpsideDown
}
If I have read the accepted answer, I wouldn't have to write my own. My version is longer but only require app delegate's application(_:supportedInterfaceOrientationsFor:) method. It maybe suitable in situation where you can't change or prefer not to change the target view controllers. For example, a third party view controller.
I was inspired from Apple's official doc: supportedInterfaceOrientations
My app runs as Portrait on iPhone and all orientations on iPad. I only want one view controller (a JTSImageViewController presents an image for larger view) to be able to rotate.
Info.plist
Supported interface orientations = Portrait
Supported interface orientations (iPad) = Portrait, PortraitUpsideDown, LandscapeLeft, LandscapeRight
If app delegate implemented application(_:supportedInterfaceOrientationsFor:), Info.plist maybe ignored. However, I didn't verify that.
Swift 4
func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
// Early return for iPad
if UIDevice.current.userInterfaceIdiom == .pad {
return [.all]
}
// Search for the visible view controller
var vc = window?.rootViewController
// Dig through tab bar and navigation, regardless their order
while (vc is UITabBarController) || (vc is UINavigationController) {
if let c = vc as? UINavigationController {
vc = c.topViewController
} else if let c = vc as? UITabBarController {
vc = c.selectedViewController
}
}
// Look for model view controller
while (vc?.presentedViewController) != nil {
vc = vc!.presentedViewController
}
print("vc = " + (vc != nil ? String(describing: type(of: vc!)) : "nil"))
// Final check if it's our target class. Also make sure it isn't exiting.
// Otherwise, system will mistakenly rotate the presentingViewController.
if (vc is JTSImageViewController) && !(vc!.isBeingDismissed) {
return [.allButUpsideDown]
}
return [.portrait]
}
Your code in the desired VC will not work.
I've managed this by adding the following code in the AppDelegate
var autoRotation: Bool = false
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
return autoRotation ? .AllButUpsideDown : .Portrait
}
And then you can make an helper class and add this method:
class func setAutoRotation(value: Bool) {
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
appDelegate.autoRotation = value
}
}
Finally in your desired VC, you can call setAutoRotation(true) on didLoad and
setAutoRotation(false) on willDissapear.
This can also achieved by subclassing the UINavigationController. You can find the answer here.
Hope it helps

Resources