Disable rotation for View Controller in Navigation Controller - ios

First of all, this isn't a duplicate. I've looked at all of the questions related to this on SO and none of them work for me. Hopefully it's just because I'm new to iOS development but I suspect this isn't possible in my case. I've attached a picture with the view controller circled that I want to disable rotation for.
I've already tried: Subclassing the view controller that I want to disable rotation for and using the shouldAutoRotate method by setting it to NO. Apparently this doesn't work because it's the navigation controller that dictates whether its view controllers can rotate. So, I subclassed UINavigationController and used this class in storyboard instead of the default navigation controller. In this class I've set shouldAutoRotate to NO. It still doesn't work. Don't really know what I'm doing wrong.
When I extend the root view controller with my view controller class with shouldAutoRotate set to NO, it disables rotation...for the whole app. This is not what I want. I only want the rotation to be disabled for the view controller circled in the picture.
Thanks in advance!

Add your AppDelegate.h
#property (nonatomic , assign) bool blockRotation;
AppDelegate.m
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (self.blockRotation) {
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskAll;
}
In view Controller that you want to disable rotation
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate* shared=[UIApplication sharedApplication].delegate;
shared.blockRotation=YES;
}
-(void)viewWillDisappear:(BOOL)animated{
AppDelegate* shared=[UIApplication sharedApplication].delegate;
shared.blockRotation=NO;
}
Swift 4.2 and later:
In your AppDelegate.swift:
var blockRotation = false
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if blockRotation {
return .portrait
}
return .all
}
In your viewController:
override func viewDidLoad() {
super.viewDidLoad()
AppDelegate.shared.blockRotation = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
AppDelegate.shared.blockRotation = false
}

Thanks to #ozgur for the fix which worked for me. I would 'vote up', but apparently I'm not good enough (whatever!) to vote whether a fix works or not. Anyway, here it is in Swift:
In AppDelegate.swift:
class AppDelegate: UIResponder, UIApplicationDelegate {
var blockRotation: Bool = false
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
if (self.blockRotation) {
println("supportedInterfaceOrientations - PORTRAIT")
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
} else {
println("supportedInterfaceOrientations - ALL")
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
In the ViewController that you want to block rotation, add UIApplicationDelegate to your class...
class LoginViewController: UIViewController, UITextFieldDelegate, UIApplicationDelegate {
and then create a reference to the AppDelegate...
var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
In viewDidLoad, set appDelegate.blockRotation = true:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
appDelegate.blockRotation = true
}
In viewWillAppear, set the orientation to force the device to the chosen orientation (Portrait in this example):
override func viewWillAppear(animated: Bool) {
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
Then in viewWillDisappear, or in prepareForSegue, set appDelegate.blockRotation = false:
override func viewWillDisappear(animated: Bool) {
appDelegate.blockRotation = false
}
This worked for me in this exact (multiple view controllers in a Navigation Controller) scenario, after many hours of reading other solutions on this site. Thanks again to #ozgur - I'd up-vote your answer if I could!
Happy trails!

You have to check in your root view controller is rotation allowed in current top controller and return YES or NO in supportedInterfaceOrientations method. So it should be like the following code in your root controller (adapt the code to your case):
- (NSUInteger)supportedInterfaceOrientations
{
return [self.navigationController.topViewController supportedInterfaceOrientations];
}
Then in each view controller add supported interface orientations, for example:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}

For those of you using Swift 2, you can follow Andre's answer, but in step one, use the code below in your AppDelegate.swift:
class AppDelegate: UIResponder, UIApplicationDelegate {
var blockRotation: Bool = false
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if (self.blockRotation) {
print("supportedInterfaceOrientations - PORTRAIT")
return UIInterfaceOrientationMask.Portrait
} else {
print("supportedInterfaceOrientations - ALL")
return UIInterfaceOrientationMask.All
}
}
supportedInterfaceOrientationsForWindow: now returns a UIInterfaceOrientationMask instead of an Int.

My case has 3 view controller:
- first view controller: portrait
- second view controller: landscape right (has navigation controller and was presented by first view controller)
- third view controller: portrait (has navigation controller and was pushed by second view controller )
And this is my solution in swift 3:
------------------------------------
At AppDelegate:
- Add this property to save your setting
private var orientation: UIInterfaceOrientationMask = .portrait
-Then create a function to set rotate for your device:
func rotateScreen(orientation: UIInterfaceOrientationMask) {
self.orientation = orientation
var value = 0;
if orientation == .landscapeRight {
value = UIInterfaceOrientation.landscapeRight.rawValue
}else {
value = UIInterfaceOrientation.portrait.rawValue
}
UIDevice.current.setValue(value, forKey: "orientation")
}
- Finally, implement support orientation method:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.orientation
}
--------------------------------
Then you can call like this before display destination view controller
(UIApplication.shared.delegate as! AppDelegate).rotateScreen(orientation: .landscapeRight)
For example, in my case: When user tap on button at the First to present the Second, I'll do like this
(UIApplication.shared.delegate as! AppDelegate).rotateScreen(orientation: .landscapeRight)
self.present(navigation, animated: true, completion: nil)
And the close button at the Second I'll do like this
(UIApplication.shared.delegate as! AppDelegate).rotateScreen(orientation: .portrait)
self.dismiss(animated: true, completion: nil)
That's all! You could never mind using navigation controller or not.
Hope this helps you guys ^__^!

UINavigationController+Rotation Category
When u usign navigation controller SupportedInterfaceOrientaions of ViewController will not work adding this category to ur Project will get the response from your viewController also instead of NavigationController alone.

In my case I have the view controller embedded in a navigation controller, and most of the solutions don't work because the viewcontroller depends on the navigation controller orientation. For this when you create the instance of the view controller you have to cast it to UINavigationController :
let theViewController = UIViewController()
if let navController = theViewController as? UINavigationController {
navController.delegate = self
}
And then add this extension:
extension PlaySplashViewController: UINavigationControllerDelegate {
func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
}
In this case this going to set portrait orientation for the navigation controller so the view controller going to use this orientation too.

Related

Preventing rotation in a specific view controller in iOS 11

I am trying to prevent rotation (lock it to say, portrait) in a specific VC that is
embedded in a navigation controller.
I am currently doing this:
To UINavigationController
extension UINavigationController {
public override func supportedInterfaceOrientations() -> Int {
return visibleViewController.supportedInterfaceOrientations()
}
}
In my VC:
class ViewController: UIViewController {
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}
}
However, I have issues when we go to another VC (embedded in another nav controller) is presented which supports both landscape and portrait. Suppose, the user rotates in the new screen to landscape. And clicks back to go to original screen. The app is now presented in landscape as opposed to portrait defined in its supportedInterfaceOrientations override. How do I prevent this erroneous behaviour?
I read in iOS 11, we should use viewWillTransition(to:with:) to handle rotation (and locking as well). In UIViewController documentation
“As of iOS 8, all rotation-related methods are deprecated. Instead,
rotations are treated as a change in the size of the view controller’s
view and are therefore reported using the viewWillTransition(to:with:)
method. When the interface orientation changes, UIKit calls this
method on the window’s root view controller. That view controller then
notifies its child view controllers, propagating the message
throughout the view controller hierarchy.”
Can you give directions on how to achieve it?
You can use this cool utility that I've been using.
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
/// OPTIONAL Added method to adjust lock and rotate to the desired orientation
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
You can also rotate your screen and at the same time, lock it to that orientation. I hope this helps!
You can use supportedInterfaceOrientationsFor
Define this in your AppDelegate
var restrictRotation = Bool()
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if restrictRotation {
return .portrait
}
else {
return .all
}
}
Put below code in your ViewController
func restrictRotation(_ restriction: Bool) {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.restrictRotation = restriction
}
call above function in your ViewController ViewwillAppear like this.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.restrictRotation(true) // TRUE MEANS ONLY PORTRAIT MODE
//OR
self.restrictRotation(false) // FALSE MEANS ROTATE IN ALL DIRECTIONS
}

How to prevent ARSCNView from rotating during orientation change

I am using ARKit and I want to allow the users to use the app in both portrait and landscape mode.
I would like all UI controls to rotate on orientation change except for the ARSCNView.
I tried to transform the sceneView in the opposite direction but that didn't work.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let targetRotation = coordinator.targetTransform
let inverseRotation = targetRotation.inverted()
coordinator.animate(alongsideTransition: { (context) in
self.sceneView.transform = self.sceneView.transform.concatenating(inverseRotation)
context.viewController(forKey: UITransitionContextViewControllerKey.from)
}, completion: nil)
}
How can I prevent the scene view of the ARKit session from rotating while allowing all other UI controls to rotate on orientation change?
You cannot specify device rotation rules on view basis. It has to be set on view controller basis. This is how iOS works. Thus, to achieve what you need you have to handle this by yourself. For example, if you're showing your ARSCNView as a full screen view, then you can present it inside a custom UIViewController sub-class, and set the rotation configuration for that controller.
Setting the supported view rotations for a specific view controller can be implemented in many ways, below are some of them.
Approach #1:
You can set the supported view orientations for any UIViewController by overriding your app delegate's method application:supportedInterfaceOrientationsForWindow:.
Sample code:
// this goes into your AppDelegate...
// Basically, this checks the current visible view controller type and decide
// what orientations your app supports based on that view controller type (class)
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
let visibleViewController = self.topViewController(withRootViewController: window?.rootViewController)
// Support only portrait orientation for a specific view controller
if visibleViewController is SomeViewController {
return .portrait
}
// Otherwise, support all orientations (standard behaviour)
return .allButUpsideDown
}
// This simple helper method is to extract the exact visible view controller at
// the moment, as `window.rootViewController` could have some container controller
// like `UINavigationController` or so that holds more controllers into it
private func topViewController(withRootViewController rootViewController: UIViewController?) -> UIViewController? {
if let rootViewController = rootViewController as? UITabBarController {
return rootViewController.selectedViewController
} else if let rootViewController = rootViewController as? UINavigationController {
return rootViewController.visibleViewController
} else if let presentedViewController = rootViewController?.presentedViewController {
return topViewController(withRootViewController: presentedViewController)
}
return rootViewController
}
Reference:
application:supportedInterfaceOrientationsForWindow: method documentation
Approach #2:
Sub-class your UINavigationController and override the following property:
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return self.topViewController?.supportedInterfaceOrientations ?? .all
}
Now this always looks into your view controller's supportedInterfaceOrientations and set the supported orientations based on the return value. Doing this enables you to simply override this in any view controller you want, setting some custom value.
For example, in SomeViewController you could simply add:
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
Reference:
supportedInterfaceOrientations property documentation
Approach #3:
If you don't want to sub-class your UINavigationController like in approach #2 above, you can set your SomeViewController as the navigation controller delegate implementing navigationControllerSupportedInterfaceOrientations(:)
Sample code:
override func viewDidLoad() {
super.viewDidLoad()
// ...
self.navigationController?.delegate = self
}
// MARK: - UINavigationControllerDelegate
func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return navigationController.topViewController?.supportedInterfaceOrientations ?? .all
}
Reference:
Matt's answer here

Allow only 1 view controller to be in landscape or portrait

my app is portrait what i wanna do is allow 1 view controller to be portrait or landscape i tried using this code in AppDelegate but i am getting an error
func application(_ application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow) -> Int {
let currentViewController: UIViewController? = self.topView
// Get topmost/visible view controller
var currentViewController: UIViewController? = self.topViewController
// Check whether it implements a dummy methods called canRotate
if currentViewController?.responds(to: #selector(self.canRotate)) {
// Unlock landscape view orientations for this view controller
return .allButUpsideDown
}
// Only allow portrait (standard behaviour)
return .portrait
}
}
Does anyone know how to do that in swift?
I took the following approach in my app.
Create a custom UINavigationController with the following code & make sure that your root navigation controller is updated to use this as its class.
import UIKit
class NavigationController: UINavigationController {
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
if let _ = presentedViewController as? MyPortraitViewController {
return .portrait
}
return .allButUpsideDown
}
}
You can replace MyPortraitViewController with the class name of the view controller you wish to deviate from the global setting.
While I use .portrait in this example, you are free to use other options.
One implementation example would be to replace the class of your base navigation controller in your storyboard which is shown below.

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

Only ONE VIEW landscape mode

I finished my iOS app but I need to set only ONE view to landscape mode, the rest of the views can only be seen in portrait mode.
I'm using Xcode 5.1 and I created all of my Views by dropping in my storyboard View Controllers from the right panel, so if you are going to tell me to write some code somewhere, please tell me exactly where I need to write it.
I read one solution here UINavigationController Force Rotate but I don't know where to write that code. Do I need to create one UIViewController manually?
Swift
AppDelegate.swift
internal var shouldRotate = false
func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return shouldRotate ? .allButUpsideDown : .portrait
}
Your landscape view controller
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.shouldRotate = true // or false to disable rotation
Objective-C
AppDelegate.h
#property (assign, nonatomic) BOOL shouldRotate;
AppDelegate.m
- (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return self.shouldRotate ? UIInterfaceOrientationMaskAllButUpsideDown
: UIInterfaceOrientationMaskPortrait;
}
Your landscape view controller
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate setShouldRotate:YES]; // or NO to disable rotation
I am gonna suppose you are targeting iOS 7 here (using XCode 5.1, I think I am right).
First, you have to understand that in order to open even just one view out of over 40 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 (see screenshot below).
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,
SWIFT4
Add below code lines to your AppDelegate
var orientationLock = UIInterfaceOrientationMask.portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.orientationLock
}
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
Add below code to the Controller you want to landscape
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.landscapeRight, andRotateTo: UIInterfaceOrientation.landscapeRight)
}
override func viewWillDisappear(_ animated : Bool) {
super.viewWillDisappear(animated)
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)
}
To follow up on Yaroslav's solution, in order to allow rotation to landscape mode at only one view, the landscape view controller should have shouldRotate set to YES in its viewWillAppear method, and to NO in its viewWillDisappear.
If you only setShouldRotate to YES at the viewWillAppear, after existing this view controller, all other view controllers will be rotated as well. So, you have to setShouldRotate to NO in its viewWillDisappear to restrict the portrait view from being rotated.
You should declare shouldRotate as internal to get rid of the public property in none public class warning. internal var shouldRotate = false

Resources