Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: How do I lock a particular View in Portrait mode whilst allowing others to change orientation?

Environment: SwiftUI using Swift 5.3
Scenario: The default orientation is Portrait, LandscapeLeft & LandscapeRight per Xcode General Setting.
This allows the possibility to have landscape on demand versus having the Xcode Setting to Portrait only.
The project is using SwiftUI Lifecycle vs AppDelegate.

Goal: To have ONLY particular Views able to rotate to landscape; the majority locked in portrait.

Current Modus Operandi: The device is set for Portrait-Only mode within the current View's .upAppear{} and via onReceive{} via the device Orientation-Change Notification.

I found this the only way to actually do a momentary Portrait lock, allowing others to render for landscape.

Problem: The Orientation-Change Notification happens TOO LATE: I see the actual landscape being corrected in real time - so the image snaps back during the rotate.

Question: How to I lock a specific swiftUI View in Portrait mode, allowing others to freely change orientation?

import SwiftUI
import UIKit

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.blue
            NavigationView {
                Text("Hello, world!")
                    .padding()
                    .navigationTitle("Turkey Gizzards")
                    .navigationBarTitleDisplayMode(.inline)
            }
        }.onAppear {
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
        }.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
        }
    }
}
like image 867
Frederick C. Lee Avatar asked Sep 13 '25 16:09

Frederick C. Lee


2 Answers

There is no native SwiftUI method for doing that. It looks like for now, it is mandatory to use the AppDelegate adaptor.

Inside your App main, add this:

@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

Then add this class:

class AppDelegate: NSObject, UIApplicationDelegate {
        
    static var orientationLock = UIInterfaceOrientationMask.all //By default you want all your views to rotate freely

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

And in the specific view designed to be locked in portrait mode:

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.blue
            NavigationView {
                Text("Hello, world!")
                    .padding()
                    .navigationTitle("Turkey Gizzards")
                    .navigationBarTitleDisplayMode(.inline)
            }
        }.onAppear {
              // Forcing the rotation to portrait
             DispatchQueue.main.async {
                 AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
            UIViewController.attemptRotationToDeviceOrientation()
           
            }
        }.onDisappear {
            // Unlocking the rotation when leaving the view
            DispatchQueue.main.async {
                AppDelegate.orientationLock = UIInterfaceOrientationMask.allButUpsideDown
            
            UIViewController.attemptRotationToDeviceOrientation()
            }
        }
    }
}

You may also, depending on your needs, add another UIDevice.current.setValue(UIInterfaceOrientation.yourOrientation.rawValue, forKey: "orientation") inside the onDisappear to force the rotation when leaving the view.

like image 128
Lawris Avatar answered Sep 16 '25 18:09

Lawris


Are you using UIHostingController? Another workaround might be subclassing it to implement supportedInterfaceOrientations / shouldAutorotate as we might normally do in UIKit:

class HostingController<Content>: UIHostingController<Content> where Content: View {}
like image 24
slythfox Avatar answered Sep 16 '25 17:09

slythfox