I have the following situation (this is just a demo application with that functionality). I have a screen with a single button (navigation link) where only portrait mode is allowed. When I click the button I remove the restrictions and it can rotate to all 4 sides. When testing on iOS 16 after going back to the first screen I again restrict it to portrait only and it force rotates when it in landscape. When done on iOS 15 it gets locked on portrait but it doesn't force rotate itself. I have to rotate for it to become portrait and fix itself there. In other words if it is in landscape -> I click back -> screen stays in landscape -> I rotate it to portrait -> it get fixated there. I want to skip the part where it stays in landscape and make it work like this in iOS 16.
@main
struct AppTesting: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
NavigationView {
FirstView()
.navigationViewStyle(.stack)
.navigationBarBackButtonHidden(true)
}
}
}
}
class ViewModel: ObservableObject {
@Published var isInFullScreen = false
var cancellable: AnyCancellable?
init() {
cancellable = $isInFullScreen
.sink { status in
if status {
AppDelegate.preferredOrientation = UIDevice.current.userInterfaceIdiom == .phone ? .allButUpsideDown : .all
AppDelegate.unlockOrientation()
} else {
AppDelegate.lockOrientation(to: .portrait)
}
}
}
}
struct FirstView: View {
@StateObject var vm = ViewModel()
var body: some View {
VStack {
NavigationLink(isActive: $vm.isInFullScreen) {
SecondView(vm: vm)
} label: {
Text("Play video in full screen")
}
}
}
}
struct SecondView: View {
@ObservedObject var vm: ViewModel
@Environment(\.dismiss) var dismiss
var body: some View {
Button("Video Player View") {
withAnimation {
vm.isInFullScreen.toggle()
self.dismiss()
}
}
.navigationBarHidden(true)
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
static var orientationLock: UIInterfaceOrientationMask?
static var preferredOrientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return Self.orientationLock ?? Self.preferredOrientation
}
static func lockOrientation(to orientation: UIInterfaceOrientationMask) {
orientationLock = orientation
if #available(iOS 16, *) {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
var topController = windowScene.windows.first?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.setNeedsUpdateOfSupportedInterfaceOrientations()
}
} else {
UIViewController.attemptRotationToDeviceOrientation()
}
}
static func unlockOrientation() {
Self.orientationLock = nil
}
}
I tried using the setValue
method from UIDevice
but I read its bad practice and Apple doesn't recommend it. Also it did break my UI.
One thing I spotted is that on iOS 16 after going back to first screen and when method setNeedsUpdateOfSupportedInterfaceOrientations()
is called the then AppDelegate calls application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask
which is not happenning on iOS 15. There after lockOrientation
AppDelegate doesn't respond.