6

My app has certain orientations that are set depending on device. For iPads it is set to landscape and portrait for iPhones. This works fine except I want to allow one view to be shown in landscape for iPhone. I need the view to automatically rotate to landscape and rotate back to portrait after leaving that view. I have the following code that locks the orientation for that view but the device has to be manually rotated back and forth. Is there a way to auto-rotate the device?

.onAppear { if(deviceType == .phone){ UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") AppDelegate.orientationLock = .landscape } } .onDisappear { if(deviceType == .phone){ AppDelegate.orientationLock = .portrait } } 

2 Answers 2

12
+50

enter image description here

This can be achieved using a View extension that will update the view's orientation. Let’s make our own extension which will rotate the view on given orientation, back and forth.

extension View { @ViewBuilder func forceRotation(orientation: UIInterfaceOrientationMask) -> some View { if UIDevice.current.userInterfaceIdiom == .phone { self.onAppear() { AppDelegate.orientationLock = orientation } // Reset orientation to previous setting let currentOrientation = AppDelegate.orientationLock self.onDisappear() { AppDelegate.orientationLock = currentOrientation } } else { self } } } 

In AppDelegate, add:

class AppDelegate: NSObject, UIApplicationDelegate { static var orientationLock = UIInterfaceOrientationMask.portrait { didSet { if #available(iOS 16.0, *) { UIApplication.shared.connectedScenes.forEach { scene in if let windowScene = scene as? UIWindowScene { windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: orientationLock)) } } UIViewController.attemptRotationToDeviceOrientation() } else { if orientationLock == .landscape { UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation") } else { UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") } } } } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { return true } func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { return AppDelegate.orientationLock } } 

This little example demonstrate how you can use this extension:

struct ContentView: View { var body: some View { NavigationView { List(Orientation.allCases, id: \.self) { orientation in NavigationLink(orientation.title) { ZStack { Text(orientation.title) } .forceRotation(orientation: orientation.mask) // << Update the required orientation here.. } } } } } enum Orientation: Int CaseIterable { case landscapeLeft case landscapeRight var title: String { switch self { case .landscapeLeft: return "LandscapeLeft" case .landscapeRight: return "LandscapeRight" } } var mask: UIInterfaceOrientationMask { switch self { case .landscapeLeft: return .landscapeLeft case .landscapeRight: return .landscapeRight } } } 
Sign up to request clarification or add additional context in comments.

2 Comments

This is an excellent answer, thank you so much!
This renders the view twice as shared in the gif too. Chain OnAppear and OnDisappear to fix the issue. self.onAppear { ... }.onDisappear { ... }
1

The answer by Kush Bhavsar almost worked for me, but it failed to revert back to the previous orientations when the view disappeared. Here's a solution that does revert back to the previous orientations as expected (and I improved the naming and organization of some things to be clearer).

Create View extension supportedInterfaceOrientations:

extension View { @ViewBuilder func supportedInterfaceOrientations( _ orientations: UIInterfaceOrientationMask ) -> some View { modifier(SupportedInterfaceOrientationsModifier(orientations: orientations)) } } private struct SupportedInterfaceOrientationsModifier: ViewModifier { let orientations: UIInterfaceOrientationMask @State private var previousOrientations = UIInterfaceOrientationMask.portrait func body(content: Content) -> some View { content .onAppear() { previousOrientations = AppDelegate.supportedInterfaceOrientations AppDelegate.supportedInterfaceOrientations = orientations } .onDisappear() { AppDelegate.supportedInterfaceOrientations = previousOrientations } } } 

In AppDelegate, add static var:

 static var supportedInterfaceOrientations = UIInterfaceOrientationMask.portrait { didSet { updateSupportedInterfaceOrientationsInUI() } } 

And add AppDelegate extension:

extension AppDelegate { private static func updateSupportedInterfaceOrientationsInUI() { if #available(iOS 16.0, *) { UIApplication.shared.connectedScenes.forEach { scene in if let windowScene = scene as? UIWindowScene { windowScene.requestGeometryUpdate( .iOS(interfaceOrientations: supportedInterfaceOrientations) ) } } UIViewController.attemptRotationToDeviceOrientation() } else { if supportedInterfaceOrientations == .landscape { UIDevice.current.setValue( UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation" ) } else { UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") } } } func application( _ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow? ) -> UIInterfaceOrientationMask { Self.supportedInterfaceOrientations } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.