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")
}
}
}
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.
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 {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With