I'm trying to lock the rotation of my application using a bunch of methods that apple have now deprecated over the years and I'm at a loss.
There isn't a SceneDelegate or AppDelegate in Xcode projects anymore using SwiftUI meaning that I can't use any of the previous methods people came up with and programming the solution in SwiftUI: How do I lock a particular View in Portrait mode whilst allowing others to change orientation?. When I try to implement this solution into my program, Xcode returns a warning BUG IN CLIENT OF UIKIT: Setting UIDevice.orientation is not supported. Please use UIWindowScene.requestGeometryUpdate(_:)
Here is my current code
app.swift
import SwiftUI
@main
struct testApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
//Main code here
}
}
}
mainView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
// rest of code is here
}
}.onAppear {
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") // Forcing the rotation to portrait
AppDelegate.orientationLock = .portrait // And making sure it stays that way
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
static var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return AppDelegate.orientationLock
}
}
As the warning message suggested, you can have a view request a particular orientation by digging out the UIWindowScene
and requesting a geometry update (requires iOS 16).
For example, this can be done in .onAppear
:
.onAppear {
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
scene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in
// Handle denial of request.
}
}
}
The orientation will stay that way, even when leaving the view. So to have it go back to the current physical orientation, you can add an .onDisappear
callback too:
.onDisappear {
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
scene.requestGeometryUpdate(.iOS(interfaceOrientations: .all))
}
}
If the user switches the physical device orientation after the view has appeared, then the physical orientation takes effect. To override this, you can use a GeometryReader
to measure the screen size and add an .onChange
callback to respond to changes:
private func requestOrientations(_ orientations: UIInterfaceOrientationMask) {
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
scene.requestGeometryUpdate(.iOS(interfaceOrientations: orientations)) { error in
// Handle denial of request.
}
}
}
var body: some View {
GeometryReader { proxy in
MyPortraitView()
.onAppear {
requestOrientations(.portrait)
}
.onChange(of: proxy.size) {
requestOrientations(.portrait)
}
.onDisppear {
requestOrientations(.all)
}
}
}
Note that changes to the view orientation are animated, so if .onChange
is used to override a change to the physical orientation, the user experience is not that great.