0

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 1
    You can try this answer for nearly the same question https://stackoverflow.com/a/73809411/16176140 – Kirill Yermak Feb 08 '23 at 09:29
  • @KirillYermak It is recommending me to do it with `UIDevice.current.setValue`. When I use that in particular it breaks my UI. Also its not good practice to force set value like that. There should be something with the method `attemptRotationToDeviceOrientation()` because when I call this method in iOS 16 it says use `setNeedsUpdateOfSupportedInterfaceOrientations()` because the other one is no longer available in 16. – Boyan Pavlov Feb 08 '23 at 09:35

0 Answers0