'SwiftUI Force Portrait On All Except One View

I have a SwiftUI project. For all but one of the views, I want to allow portrait and only portrait mode. For only one view, I want to allow both portrait and landscape. There are some resources on Swift but I couldn't find any on SwiftUI.

Did anyone find a way to accomplish this?



Solution 1:[1]

I had to do something similar to this. Here's our approach.

We set the project orientation to only support Portrait mode.

Then in your AppDelegate add an instance variable for orientation and conform to the supportedInterfaceOrientationsFor delegate method.

static var orientationLock = UIInterfaceOrientationMask.portrait

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return AppDelegate.orientationLock
}

Then when your about to present your landscape view perform the following actions:

AppDelegate.orientationLock = UIInterfaceOrientationMask.landscapeLeft
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()

And on dismissal,

AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()

Hope this helps!

Solution 2:[2]

A couple of tweaks to the answer above:

In AppDelegate as Jonathan's answer above:

static var orientationLock = 
UIInterfaceOrientationMask.portrait

func application(_ application: UIApplication, 
supportedInterfaceOrientationsFor window: 
UIWindow?) -> UIInterfaceOrientationMask {
return AppDelegate.orientationLock
}

Then in the 'destinationView' - the one that is landscape:

import SwiftUI

struct DestinationView: View {

var body: some View {
    Group {

        Text("Hello")

    }.onAppear {
        AppDelegate.orientationLock = UIInterfaceOrientationMask.landscapeLeft
        UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
        UINavigationController.attemptRotationToDeviceOrientation()
    }
    .onDisappear {
        DispatchQueue.main.async {
            AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
            UINavigationController.attemptRotationToDeviceOrientation()
        }
    }
}
}

Note the .rawValue in the UIDevice line which gets rid of the 'LongValue' error. Also, in the .onDisappear I had to use DispatchQueue.main.async in order to avoid an error when returning back to previous view which is in portrait.

Solution 3:[3]

If your app is using SwiftUI lifeCycle, the app is launched using a custom struct that conforms to the App protocol. Your app doesn't have AppDelegate which is needed to implement the above solutions.

To make your own AppDelegate try this code in your appnameApp.swift

@main
struct appnameApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    
    static var orientationLock = UIInterfaceOrientationMask.portrait
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // something to do
        return true
    }
    
    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window:UIWindow?) -> UIInterfaceOrientationMask {
        return AppDelegate.orientationLock
    }
}

Source: https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-an-appdelegate-to-a-swiftui-app

Solution 4:[4]

Plus, you can go further and use Jonathan's code to create an extension for the View class and simply call this extension on the classes you would like to rotate.

extension View {
    func unlockRotation() -> some View {
        onAppear {
            AppDelegate.orientationLock = UIInterfaceOrientationMask.allButUpsideDown
            UIViewController.attemptRotationToDeviceOrientation()
        }
        .onDisappear {
            AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
            UIViewController.attemptRotationToDeviceOrientation()
        }
    }
}

Solution 5:[5]

use AppUtility from https://stackoverflow.com/a/41811798/10330141

in SwiftUI

create custom UIHostingController

 class OrientationHostingController : UIHostingController<AnyView> {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        AppUtility.lockOrientation(.portrait)
        
    }
 }




let rootView = OrientationHostingController(rootView: AnyView(SwiftUIView()))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Fadi
Solution 2 Zenman C
Solution 3
Solution 4 Alex G. Luque
Solution 5 John Theerawit