is Force View Controller Orientation working in iOS 16 beta - ios

According to the iOS & iPadOS 16 Beta 3 Release Notes:- Attempting to set an orientation on UIDevice via setValue:forKey: isn’t supported and no longer works. Instead, they say use: preferredInterfaceOrientationForPresentation.
In my case, force view controller orientation is not working in iOS 16 beta either by using preferredInterfaceOrientationForPresentation or requestGeometryUpdate.
Previously, UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation") was working fine.

It works for me.
In AppDelegate,
var orientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return orientation
}
In view controller,
(UIApplication.shared.delegate as? AppDelegate)?.orientation = .landscapeRight
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight))
UIApplication.navigationTopViewController()?.setNeedsUpdateOfSupportedInterfaceOrientations()
In Helper,
extension UIApplication {
class func navigationTopViewController() -> UIViewController? {
let nav = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController
return nav?.topViewController
}
}

My problem with my code below is that I'm trying to do it when closing a modal view and the view under it are not updated quick enough. If I put the requestGeometryUpdate on a separate button then when I close the view it work.
if #available(iOS 16.0, *) {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
}

It works for me:
import Foundation
import UIKit
extension UIViewController {
func setDeviceOrientation(orientation: UIInterfaceOrientationMask) {
if #available(iOS 16.0, *) {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
} else {
UIDevice.current.setValue(orientation.toUIInterfaceOrientation.rawValue, forKey: "orientation")
}
}
}
extension UIInterfaceOrientationMask {
var toUIInterfaceOrientation: UIInterfaceOrientation {
switch self {
case .portrait:
return UIInterfaceOrientation.portrait
case .portraitUpsideDown:
return UIInterfaceOrientation.portraitUpsideDown
case .landscapeRight:
return UIInterfaceOrientation.landscapeRight
case .landscapeLeft:
return UIInterfaceOrientation.landscapeLeft
default:
return UIInterfaceOrientation.unknown
}
}
}
How to use it?
Just call it on your UIViewController:
setDeviceOrientation(orientation: .landscapeRight)
EDIT
More completed solution:
import UIKit
final class DeviceOrientation {
static let shared: DeviceOrientation = DeviceOrientation()
// MARK: - Private methods
private var windowScene: UIWindowScene? {
return UIApplication.shared.connectedScenes.first as? UIWindowScene
}
// MARK: - Public methods
func set(orientation: UIInterfaceOrientationMask) {
if #available(iOS 16.0, *) {
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
} else {
UIDevice.current.setValue(orientation.toUIInterfaceOrientation.rawValue, forKey: "orientation")
}
}
var isLandscape: Bool {
if #available(iOS 16.0, *) {
return windowScene?.interfaceOrientation.isLandscape ?? false
}
return UIDevice.current.orientation.isLandscape
}
var isPortrait: Bool {
if #available(iOS 16.0, *) {
return windowScene?.interfaceOrientation.isPortrait ?? false
}
return UIDevice.current.orientation.isPortrait
}
var isFlat: Bool {
if #available(iOS 16.0, *) {
return false
}
return UIDevice.current.orientation.isFlat
}
}
extension UIInterfaceOrientationMask {
var toUIInterfaceOrientation: UIInterfaceOrientation {
switch self {
case .portrait:
return UIInterfaceOrientation.portrait
case .portraitUpsideDown:
return UIInterfaceOrientation.portraitUpsideDown
case .landscapeRight:
return UIInterfaceOrientation.landscapeRight
case .landscapeLeft:
return UIInterfaceOrientation.landscapeLeft
default:
return UIInterfaceOrientation.unknown
}
}
}
How to use it:
DeviceOrientation.shared.set(orientation: .portrait)

I noticed my issue seems like resolved by calling method below:
[UIViewController setNeedsUpdateOfSupportedInterface
You may give it a try.

I tried all above solutions seem they're not 100% percent working. After through this post
https://developer.apple.com/forums/thread/707735 i got the hint. Let's try this below code. It’s worked for me.
if #available(iOS 16.0, *) {
DispatchQueue.main.async {
UIViewController.attemptRotationToDeviceOrientation()
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation)) { error in
print(error)
print(windowScene?.effectiveGeometry)
}
navigationController?.topViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
}
}

setValue:forKey is a method of old NSObject (NSKeyValueCoding). It's not official documented and supported by UIDevice class. Using it is considering using a private api. Apple can terminate it anytime they want.

Apple released new API which is replaced with setValue:forKey:"orientation".
Apple update
guard let windowScene = view.window?.windowScene else { return }
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in
// Handle denial of request.
}
But I am having problem about UIDevice.orientationDidChangeNotification , it is not working

Following Apple's documentation you need to
Requests an update to the window scene’s geometry using the specified geometry preferences object.
https://developer.apple.com/documentation/uikit/uiwindowscene/3975944-requestgeometryupdate/
So using the code in the example, you can change the way we set the orientation in our views using requestGeometryUpdate and using as well setNeedsUpdateOFSupportedInterface
public extension UIViewController {
func deviceOrientation(orientation: UIInterfaceOrientationMask) {
if #available(iOS 16.0, *) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
else { return }
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
self.setNeedsUpdateOfSupportedInterfaceOrientations()
}
}
}

Related

Screen goes black after rotating the device only in ios 16

Some pages in the app support landscape mode.
After the ios 16 version was released
When turning the device horizontally, the screen becomes black, and only after turning horizontally again to the other side does the screen return to itself and the buttons function.
It is important to note that when I run on a device with iOS version 16 everything works great
I tried everything and could not find a solution
Has anyone tried this?
In my viewController:
var orientationType : UIInterfaceOrientationMask
{
guard let parent = self.parent else { return .allButUpsideDown }
guard parent is BaseChannelViewController else { return .allButUpsideDown }
return .portrait
}
public func animate(fullscreenPresentation completionBlock : AnimationCompletion?)
{
guard displayMode != .fullscreen else { completionBlock?(); return }
guard !isDisplayedModaly else { completionBlock?(); return }
switch transitionType
{
case .default : fallthrough
case .minimizable: fallthrough
case .interactive:
guard let transition = customTransitioningDelegate as? GAVideoPlayerZoomTransition else { break }
guard let presentingVC = dataSource?.presentingViewController(for: self) else { break }
let originFrame = dataSource?.initialFrameForTransitionToFullScreenDisplay(from: displayMode.initialScreenMode, for: self) ?? self.view.frame
transition.currentFrame = originFrame
NotificationCenter.default.post(name: .GAVideoPlayerViewControllerWillEnterFullscreen, object: self)
hideControlsNoAnimation()
if let _ = parent
{
removeFromParent()
}
// FIXME - Need this to prevent bad access crash, will show un balanced animation calls in the logger
view.removeFromSuperview()
let clientOrientationRawValue = clientOrientation.rawValue
presentingVC.present(self, animated: true) { [weak self] in
self?.displayMode = .fullscreen
let currentOrientationRawValue = UIDevice.current.orientation.rawValue
UIDevice.current.setValue(clientOrientationRawValue, forKey: "orientation")
UIDevice.current.setValue(currentOrientationRawValue, forKey: "orientation")
completionBlock?()
}
break
default: break
}
}
fileprivate var clientOrientation: UIDeviceOrientation
{
return UIDeviceOrientation(interfaceOrientationMask: clientSupportedInterfaceOrientations)
}
In AppDelegate:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask
{
guard let viewController = UIApplication.shared.topViewController() as? CustomOrientationViewController else { return .portrait }
return viewController.orientationType
}

How do you make an app's orientation dependent on a URL using requestGeometryUpdate?

I am new to Swift and iOS development. I am trying to wrap a web app where the orientation is dependent on the URL. I have the code working with Stack Overflow as an example where "https://stackoverflow.com" displays in portrait and all other pages change to landscape after being loaded. I have a URL observer that triggers when the URL changes and calls requestGeometryUpdate. I'm running into the following problem:
When changing the orientation with requestGeometryUpdate, the orientation changes, but if the device is physically rotated after the change, the orientation changes again. I would like to make the orientation change locked and permanent until a new page is loaded.
Any help would be much appreciated. I have attached my code below:
import SwiftUI
import WebKit
struct TestView: View {
private let urlString: String = "https://stackoverflow.com/"
var body: some View {
TestWebView(url: URL(string: urlString)!)
.background(Color.black)
.scrollIndicators(.hidden)
.ignoresSafeArea([.all])//stretchs webview over notch on iphone
.defersSystemGestures(on:.bottom)//deprioritizes multitasking indicator
.statusBar(hidden: true)//hides time and battery
}
}
class TestController: UIViewController {
var webview: WKWebView!
var webViewURLObserver: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
let winScene = UIApplication.shared.connectedScenes.first
let windowScene = winScene as! UIWindowScene
webview = WKWebView(frame: self.view.frame)
webview.autoresizingMask = [.flexibleWidth,.flexibleHeight]//makes webview fit screen in portrait and landscape
self.view.addSubview(self.webview)
webViewURLObserver = self.webview.observe(\.url, options: .new) { webview, change in
let url=change.newValue!!;//! converts from optional to string
print(url)
let arr = url.absoluteString.split(separator: "stackoverflow.com").map(String.init)
var portrait=false
if(arr.count>1){
let path = arr[1];
if path=="/"{
portrait=true
}
}
if portrait==true {
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in print(error)}
}
else{
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in print(error)}
}
self.setNeedsUpdateOfSupportedInterfaceOrientations()
}
}
}
// WebView Struct
struct TestWebView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: Context) -> TestController {
let webviewController = TestController()
return webviewController
}
func updateUIViewController(_ webviewController: TestController, context: Context) {
let request = URLRequest(url: url)
webviewController.webview.scrollView.contentInsetAdjustmentBehavior = .never
webviewController.webview.load(request)
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
There is an answer here that may help you with this: SwiftUI: How do I lock a particular View in Portrait mode whilst allowing others to change orientation?
Basically what your want to do is add this AppDelegate variable/class to your App Main. See Below:
struct WebApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var app_delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
static var allowed_orientations = UIInterfaceOrientationMask.portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return AppDelegate.allowed_orientations
}
}
Then in your ViewDidLoad you will add the orientation lock where you are setting the portrait variable.
if portrait==true {
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in print(error)}
}
else{
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in print(error)}
}
AppDelegate.allowed_orientations = portrait ? .portrait : .landscape
self.setNeedsUpdateOfSupportedInterfaceOrientations()

connectedScenes and UIWindowScene to support IOS 11

I have this iOS app which trying to downgrade to support older iPhones with iOS version 11.0, but ran into few problems.
I have the below line of code in AppDelegate which shows error 'connectedScenes' is only available in iOS 13.0 or newer and 'UIWindowScene' is only available in iOS 13.0 or newer
var keyWindowDev = UIApplication.shared.connectedScenes.filter({$0.activationState == .foregroundActive}).map({$0 as? UIWindowScene}).compactMap({$0}).first?.windows.filter({$0.isKeyWindow}).first
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,UIWindowSceneDelegate, UNUserNotificationCenterDelegate,MessagingDelegate {
var window: UIWindow?
//....
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
}
}
//Below is keyWindowDev usage in AppDelegate
extension UIApplication {
class func topViewController(base: UIViewController? = keyWindowDev?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
return base
}
}
And keyWindowDev is also used to set SVProgressHUD Container View around the app
class HomeVC: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
SVProgressHUD.setContainerView(keyWindowDev)
}
}
Please how to change the line of code in keyWindowDev to support iOS 11?
You should be able to use something like this:
var keyWindowDev: UIWindow? = {
if #available(iOS 13.0, *) {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }.first?.windows
.first(where: \.isKeyWindow)
} else {
return UIApplication.shared.keyWindow
}
}()
I made some optimizations to your original code.

Rotation only in one ViewController

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
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)
}
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
}
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)
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
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 {
}
Use the shouldAutorotate and the supportedInterfaceOrientations method in the ViewController you want to display in landscape and portrait mode:
This method should override the storyboard-settings.
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
return UIInterfaceOrientation.Portrait.rawValue | UIInterfaceOrientation.LandscapeLeft.rawValue | UIInterfaceOrientation.LandscapeRight.rawValue
}
I just faced a very similar problem where I wanted to present a video player in portrait and landscape mode whereas the rest of the app is portrait only. My main problem was that when I dismissed the video vc in landscape mode the presenting vc was only briefly in landscape mode.
As pointed out in the comment to #Lyndsey Scott's answer this can be circumvented by disallowing transitions while in landscape mode, but by combining this and this I've found a better and more generic solution (IMO). 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(){}
Swift 5 using Marker protocol
Combined version of several answers here, done in what I think is a more readable/elegant implementation. (Derived from earlier answers here, not original work by me!)
protocol RotatableViewController {
// No content necessary, marker protocol
}
class MyViewController: UIViewController, RotatableViewController {
// normal content... nothing more required
}
extension AppDelegate {
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
guard
let rootVc = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController),
rootVc.isBeingDismissed == false,
let _ = rootVc as? RotatableViewController
else {
return .portrait // Some condition not met, so default answer for app
}
// Conditions met, is rotatable:
return .allButUpsideDown
}
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
}
}
Swift 3:
Add code to 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
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
}
Then :
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillDisappear(_ animated : Bool) {
super.viewWillDisappear(animated)
if (self.isMovingFromParentViewController) {
UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation")
}
}
func canRotate() -> Void {}
}
http://www.jairobjunior.com/blog/2016/03/05/how-to-rotate-only-one-view-controller-to-landscape-in-ios-slash-swift/
SWIFT 4
For UITabBarController can we use this line of code in AppDelegate.swift.
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let tabBarController = window?.rootViewController as? UITabBarController {
if let tabBarViewControllers = tabBarController.viewControllers {
if let projectsNavigationController = tabBarViewControllers[1] as? UINavigationController {
if projectsNavigationController.visibleViewController is PickerViewController //use here your own ViewController class name {
return .all
}
}
}
}
return .portrait
}
Solution Swift 5.1
In App delegate implement this method
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let topController = UIApplication.topViewController() {
if topController.isKind(of: YourSpecificViewController.self) {
return .all
}
return .portrait
}
return .portrait
}
Then add this extension to get the top most ViewController
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
} else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return topViewController(base: selected)
} else if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
Just wanted to share my solution as someone who has spent too much time rotating one view controller in the app:
var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { get }
overriding this UIViewController method helped me do what I need.
On the view controller that you want to rotate do this for landscape left rotation:
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return UIInterfaceOrientation.landscapeLeft
}
Make sure you enable rotation in the desired directions from the project settings:
And add this to AppDelegate to disable other screens' rotation:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return .portrait
}
Swift 5
Another answer, this one covers the isBeingDismissed case.
In AppDelegate:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if
let vvc = navigationController?.visibleViewController,
vvc is YOURViewControllerClassName &&
!vvc.isBeingDismissed
{
return UIInterfaceOrientationMask.landscape
} else {
return UIInterfaceOrientationMask.portrait
}
}
None of these answers worked for me. Fundamentally, AppDelegate's method does not allow specification on which viewController. So either the topMost ViewController is rotatable, in which case the whole view controller hierarchy gets rotated, or nothing gets rotated.
However, I did find a promising answer in Child View Controller to Rotate While Parent View Controller Does Not
It references https://developer.apple.com/library/archive/qa/qa1890/_index.html

Force landscape mode in one ViewController using Swift

I am trying to force only one view in my application on landscape mode,
I am calling:
override func shouldAutorotate() -> Bool {
print("shouldAutorotate")
return false
}
override func supportedInterfaceOrientations() -> Int {
print("supportedInterfaceOrientations")
return Int(UIInterfaceOrientationMask.LandscapeLeft.rawValue)
}
override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return UIInterfaceOrientation.LandscapeLeft
}
The view is launched in the portrait mode, and keep rotating when I change the device orientation. The shouldAutorotate() method is never called.
Any help would be appreciated.
It may be useful for others, I found a way to force the view to launch in landscape mode:
Put this in the viewDidLoad():
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
and,
override var shouldAutorotate: Bool {
return true
}
Swift 4
override func viewDidLoad() {
super.viewDidLoad()
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeLeft
}
override var shouldAutorotate: Bool {
return true
}
If your view is embedded in a navigation controller, the above alone won't work. You have to cascade up by the following extension after the class definition.
extension UINavigationController {
override open var shouldAutorotate: Bool {
get {
if let visibleVC = visibleViewController {
return visibleVC.shouldAutorotate
}
return super.shouldAutorotate
}
}
override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{
get {
if let visibleVC = visibleViewController {
return visibleVC.preferredInterfaceOrientationForPresentation
}
return super.preferredInterfaceOrientationForPresentation
}
}
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask{
get {
if let visibleVC = visibleViewController {
return visibleVC.supportedInterfaceOrientations
}
return super.supportedInterfaceOrientations
}
}}
Swift 3
override func viewDidLoad() {
super.viewDidLoad()
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
private func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.landscapeLeft
}
private func shouldAutorotate() -> Bool {
return true
}
Swift 4 , Tested in iOS 11
You can specify the orientation in projectTarget -> General -> DeploymentInfo(Device Orientation) -> Portrait (Landscapeleft and Landscaperight are optional)
AppDelegate
var myOrientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return myOrientation
}
LandScpaeViewController
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.myOrientation = .landscape
}
OnDismissButtonTap
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.myOrientation = .portrait
Thats it. :)
Using Swift 2.2
Try:
let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
Followed By:
UIViewController.attemptRotationToDeviceOrientation()
From Apple's UIViewController Class Reference:
Some view controllers may want to use app-specific conditions to determine what interface orientations are supported. If your view controller does this, when those conditions change, your app should call this class method. The system immediately attempts to rotate to the new orientation.
Then, as others have suggested, override the following methods as appropriate:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.LandscapeLeft
}
override func shouldAutorotate() -> Bool {
return true
}
I was having a similar issue with a signature view and this solved it for me.
In AppDelegate add this:
//Orientation Variables
var myOrientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return myOrientation
}
Add this in viewController, that want to change orientation:
override func viewDidLoad() {
super.viewDidLoad()
self.rotateToLandsScapeDevice()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.rotateToPotraitScapeDevice()
}
func rotateToLandsScapeDevice(){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.myOrientation = .landscapeLeft
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
UIView.setAnimationsEnabled(true)
}
func rotateToPotraitScapeDevice(){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.myOrientation = .portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UIView.setAnimationsEnabled(true)
}
For me, the best results came from combining Zeesha's answer and sazz's answer.
Add the following lines to AppDelegate.swift:
var orientationLock = UIInterfaceOrientationMask.portrait
var myOrientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return myOrientation
}
Add the following line to your view controller class:
let appDel = UIApplication.shared.delegate as! AppDelegate
Add the following lines to your view controller's viewDidLoad():
appDel.myOrientation = .landscape
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
(optional) Add this line to your dismiss function:
appDel.myOrientation = .portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
What these lines of code do is set the default orientation to portrait, rotate it landscape when the view controller loads, and then finally reset it back to portrait once the view controller closes.
Overwrite (in ViewController):
override public var shouldAutorotate: Bool {
return false
}
override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeRight
}
override public var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeRight
}
Hint for ios 13. As of ios 13, VC has different modalPresentationStyle as .automatic and device present modal view instead of Full-Screen VC. To fix this one must set modalPresentationStyle to .fullScreen. Example:
let viewController = YourViewController()
viewController.modalPresentationStyle = .fullScreen
I needed to force one controller into portrait orientation. Adding this worked for me.
swift 4 with iOS 11
override var supportedInterfaceOrientations : UIInterfaceOrientationMask{
return .portrait
}
I faced a similar issue in my project. It only has support for portrait. The ViewController structure is that, Navigation contained a controller (I called A), and a long Scrollview in A controller. I need A(portrait) present to B(landscape right).
In the beginning I tried the method below and it seemed to work but eventually I found a bug in it.
Swift 5 & iOS12
// In B controller just override three properties
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.landscapeRight
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeRight
}
And then something become strange. When controller B dismiss to controller A. The ScrollView in controller A has been slid some point.
So I used another method, so I rotate the screen when viewWillAppear. You can see the code for that below.
// In controller B
// not need override shouldAutorotate , supportedInterfaceOrientations , preferredInterfaceOrientationForPresentation
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let appDel = UIApplication.shared.delegate as! AppDelegate
appDel.currentOrientation = .landscapeRight
UIDevice.current.setValue( UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
}
//in viewWillDisappear rotate to portrait can not fix the bug
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
let appDel = UIApplication.shared.delegate as! AppDelegate
appDel.currentOrientation = .portrait
UIDevice.current.setValue( UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation() //must call
super.dismiss(animated: true, completion: nil)
}
// in AppDelegate
// the info plist is only supported portrait also, No need to change it
var currentOrientation : UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.currentOrientation
}
Works in Swift 2.2
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if self.window?.rootViewController?.presentedViewController is SignatureViewController {
let secondController = self.window!.rootViewController!.presentedViewController as! SignatureViewController
if secondController.isPresented {
return UIInterfaceOrientationMask.LandscapeLeft;
} else {
return UIInterfaceOrientationMask.Portrait;
}
} else {
return UIInterfaceOrientationMask.Portrait;
}
}
Swift 3. This locks the orientation each time the user re-opens the app.
class MyViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
// Receive notification when app is brought to foreground
NotificationCenter.default.addObserver(self, selector: #selector(self.onDidBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
// Handle notification
func onDidBecomeActive() {
setOrientationLandscape()
}
// Change orientation to landscape
private func setOrientationLandscape() {
if !UIDevice.current.orientation.isLandscape {
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey:"orientation")
UIViewController.attemptRotationToDeviceOrientation()
}
}
// Only allow landscape left
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.landscapeLeft
}
/*
// Allow rotation - this seems unnecessary
private func shouldAutoRotate() -> Bool {
return true
}
*/
...
}
Swift 4
Trying to keep the orientation nothing worked but this for me:
...
override func viewDidLoad() {
super.viewDidLoad()
forcelandscapeRight()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(forcelandscapeRight), name: Notification.Name.UIDeviceOrientationDidChange, object: nil)
}
#objc func forcelandscapeRight() {
let value = UIInterfaceOrientation.landscapeRight.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
....
In ViewController in viewDidLoad Method call below function
func rotateDevice(){
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
UIView.setAnimationsEnabled(true) // while rotating device it will perform the rotation animation
}`
App Delegate File Add Below Function & Variables
//Orientation Variables
var orientationLock = UIInterfaceOrientationMask.portrait
var myOrientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { return .landscape }
According to the documentation of supportedInterfaceOrientations the shouldAutorotate method should return true or YES in Objective-C so that the supportedInterfaceOrientations are considered.
iOS 16+: requestGeometryUpdate(_:errorHandler:) API
As noted by #simonbs on Twitter, iOS 16 introduces a new API to update the current interface orientation. While in most cases, other, conventional methods will do the job, in some edge cases, they don't work (forcing the use of private APIs like suggested in this answer). Here, the new public API comes to the rescue.
The API works as follows:
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
You can optionally also pass a closure to handle errors (though I have no experience under which circumstances errors may occur):
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in
// Handle error...
}
My solution is
just added below codes in AppDelegate
enum PossibleOrientations {
case portrait
case landscape
func o() -> UIInterfaceOrientationMask {
switch self {
case .portrait:
return .portrait
case .landscape:
return .landscapeRight
}
}
}
var orientation: UIInterfaceOrientationMask = .portrait
func switchOrientation(to: PossibleOrientations) {
let keyOrientation = "orientation"
if to == .portrait && UIDevice.current.orientation.isPortrait {
return
} else if to == .landscape && UIDevice.current.orientation.isLandscape {
return
}
switch to {
case .portrait:
orientation = .portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: keyOrientation)
case .landscape:
orientation = .landscapeRight
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: keyOrientation)
}
}
And call below codes to change
override func viewDidLoad() {
super.viewDidLoad()
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.switchOrientation(to: .landscape)
}
}
or like below
#IBAction func actBack() {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.switchOrientation(to: .portrait)
}
self.navigationController?.popViewController(animated: true)
}
// below code put in view controller
// you can change landscapeLeft or portrait
override func viewWillAppear(_ animated: Bool) {
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeRight
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeRight
}
I tried many of the answers below but I'm afraid they didn't work. Especially if nav bars and tab bars are also implemented on the app. The solution that worked for me was provided by Sunny Lee on this post here:
Sunny Lee, Medium post
Which in turn is an update of this post:
Original solution
The only change I made when implementing the post's solution, was to change the part which references .allButUpsideDown to .landscapeleft
In Xcode 11 with Swift 5 I Implemented the following. But it only works when the device orientation for the project target does not include all orientations. I disabled the check for Upside Down. After this, the following code works. If all checkboxes are enabled, the code is not called;
class MyController : UINavigationController {
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
}
class CustomUIViewController : UIViewController{
override var supportedInterfaceOrientations : UIInterfaceOrientationMask{
return .landscapeLeft
}
}
class ViewController: CustomUIViewController {
.
.
.
}

Resources