LiamPalmqvist
LiamPalmqvist

Reputation: 5

Locking rotation to portrait on SwiftUI without the use of AppDelegate or SceneDelegate

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

Upvotes: 0

Views: 310

Answers (1)

Benzy Neez
Benzy Neez

Reputation: 20759

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.

Upvotes: 0

Related Questions