swiftuiorientation

SwiftUI force device rotation programmatically


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
            }
        }

Solution

  • 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
            }
        }
    }