Rotation only in one ViewController
Solution 1
This is for Swift 4 and Swift 5. You can use the follow code in your AppDelegate.swift :
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
guard let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController),
(rootViewController.responds(to: Selector(("canRotate")))) else {
// Only allow portrait (standard behaviour)
return .portrait;
}
// Unlock landscape view orientations for this view controller
return .allButUpsideDown;
}
private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
guard rootViewController != nil else { return nil }
guard !(rootViewController.isKind(of: (UITabBarController).self)) else{
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
}
guard !(rootViewController.isKind(of:(UINavigationController).self)) else{
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
}
guard !(rootViewController.presentedViewController != nil) else {
return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController
}
You can then make a custom UIViewController rotate by overriding shouldAutorotate
Solution 2
I'd recommend using supportedInterfaceOrientationsForWindow
in your appDelegate
to allow rotation only in that specific view controller, ex:
Swift 4/Swift 5
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
// Make sure the root controller has been set
// (won't initially be set when the app is launched)
if let navigationController = self.window?.rootViewController as? UINavigationController {
// If the visible view controller is the
// view controller you'd like to rotate, allow
// that window to support all orientations
if navigationController.visibleViewController is SpecificViewController {
return UIInterfaceOrientationMask.all
}
// Else only allow the window to support portrait orientation
else {
return UIInterfaceOrientationMask.portrait
}
}
// If the root view controller hasn't been set yet, just
// return anything
return UIInterfaceOrientationMask.portrait
}
Note that if that SpecificViewController
is in landscape before going to a portrait screen, the other view will still open in landscape. To circumvent this, I'd recommend disallowing transitions while that view is in landscape.
Swift 3
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
// Make sure the root controller has been set
// (won't initially be set when the app is launched)
if let navigationController = self.window?.rootViewController as? UINavigationController {
// If the visible view controller is the
// view controller you'd like to rotate, allow
// that window to support all orientations
if navigationController.visibleViewController is SpecificViewController {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
// Else only allow the window to support portrait orientation
else {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
}
// If the root view controller hasn't been set yet, just
// return anything
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
Solution 3
You can also do it in a protocol oriented way. Just create the protocol
protocol CanRotate {
}
Add the the same 2 methods in the AppDelegate in a more "swifty" way
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if topViewController(in: window?.rootViewController) is CanRotate {
return .allButUpsideDown
} else {
return .portrait
}
}
func topViewController(in rootViewController: UIViewController?) -> UIViewController? {
guard let rootViewController = rootViewController else {
return nil
}
if let tabBarController = rootViewController as? UITabBarController {
return topViewController(in: tabBarController.selectedViewController)
} else if let navigationController = rootViewController as? UINavigationController {
return topViewController(in: navigationController.visibleViewController)
} else if let presentedViewController = rootViewController.presentedViewController {
return topViewController(in: presentedViewController)
}
return rootViewController
}
And in every ViewController that you want a different behaviour, just add the protocol name in the definition of the class.
class ViewController: UIViewController, CanRotate {}
If you want any particular combination, they you can add to the protocol a variable to override
protocol CanRotate {
var supportedInterfaceOrientations: UIInterfaceOrientationMask
}
Solution 4
Sometimes when you're using a custom navigation flow (that may get really complex) the above-mentioned solutions may not always work. Besides, if you have several ViewControllers that need support for multiple orientations it may get quite tedious.
Here's a rather quick solution I found. Define a class OrientationManager
and use it to update supported orientations in AppDelegate:
class OrientationManager {
static var landscapeSupported: Bool = false
}
Then in AppDelegate put the orientations you want for that specific case:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if OrientationManager.landscapeSupported {
return .allButUpsideDown
}
return .portrait
}
Then in the ViewControllers that you want to have multiple navigations update the OrientationManager
:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
OrientationManager.landscapeSupported = true
}
Also, don't forget to update it once again when you'll be exiting this ViewController:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
OrientationManager.landscapeSupported = false
//The code below will automatically rotate your device's orientation when you exit this ViewController
let orientationValue = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(orientationValue, forKey: "orientation")
}
Hope this helps!
Update:
You may just want to add a static func
to your Orientation Support Manager
class:
static func setOrientation(_ orientation: UIInterfaceOrientation) {
let orientationValue = orientation.rawValue
UIDevice.current.setValue(orientationValue, forKey: "orientation")
landscapeSupported = orientation.isLandscape
}
Then you can call this function whenever you need to set the orientation back to portrait. That will also update the static landscapeSupported
value:
OSM.setOrientation(.portrait)
Solution 5
With everyone's ideas I wrote the most elegant way to do it I think.
Result:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return (UIApplication.getTopViewController() as? Rotatable == nil) ? .portrait : .allButUpsideDown
}
Add this extension to your project which will always be useful not only for this:
extension UIApplication {
class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return getTopViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return getTopViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return getTopViewController(base: presented)
}
return base
}
}
Create the protocol:
protocol Rotatable {}
And implement it:
class ViewController: UIViewController, Rotatable {
}
Armin Scheithauer
Developing iOS Apps and Websites. Started with iOS just some months ago and website programming since 2000.
Updated on June 06, 2022Comments
-
Armin Scheithauer almost 2 years
I am trying to rotate one view while all other views (5) are fixed to portrait. The reason is that in that one view I want the user to watch pictures which he saved before. I guess this is possible but so far I couldn't figure out how to achieve that. Can anyone help or give me a hint? I am programming that in Swift running on iOS8
-
Lyndsey Scott over 9 years@ArminScheithauer No problem :) And to be honest, I'm not convinced Christian's solution will work in all situation's because I'm having trouble producing the desired results when testing his code...
-
Lyndsey Scott over 9 years@ArminScheithauer To get Christian's answer to work you have to subclass the UINavigationViewController and use UInterfaceOrientationMask instead of UInterfaceOrientation among other things...
-
Lyndsey Scott over 9 yearsHey Christian, this answer only seems to work if you subclass the
UINavigationController
and returnself.topViewController.supportedInterfaceOrientations()
andself.topViewController.shouldAutorotate()
for thesupportedInterfaceOrientations()
andshouldAutorotate()
functions respectively. -
Himanshu Singla over 6 yearsUnable to do so for a viewcontroller which is inside a framework, as framework doesn't have a AppDelegate file
-
noob over 6 years@LyndseyScott what does that mean "disallowing transitions while that view is in landscape." Can you explain it in detail. Thanks
-
dmyma about 5 yearsI also added to
viewWillDisappear
if (self.isMovingFromParent) { UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation") }
so, the presenting vc doesn't rotate on pop. -
Nizzam almost 5 yearsI have tried your solution. Unfortunately, its not working.
-
Sara about 4 yearsI've made all of these changes but nothing is changing for me, any other suggestions?
-
Tieda Wei about 4 yearsMay I ask where is
navigationController?
coming from? -
Tieda Wei about 4 yearsIf MyRotatbleVC is presented and when it's dismissed in Landscape mode, the previous VC will in Landscape mode, instead of remaining Portrait.
-
Senocico Stelian about 3 yearsGreat! Thank you!
-
sreejesh over 2 yearsThank you very much. Your answer worked. I have been searching for solutions since two days.