Swift多屏幕间旋转锁定解锁的iOS版本适配问题
iOS 15 强制返回竖屏的解决方案
问题场景
- 初始页面仅允许竖屏显示
- 进入第二个页面后解除旋转限制,支持多方向旋转
- 返回初始页面时:
- iOS 16 会自动强制旋转回竖屏并锁定
- iOS 15 仅锁定竖屏,若当前处于横屏状态,需手动旋转设备才会固定为竖屏
现有问题
- 使用
UIDevice.setValue修改方向是苹果不推荐的做法,会导致UI异常 - iOS 16 中调用
setNeedsUpdateOfSupportedInterfaceOrientations()能触发AppDelegate的方向校验逻辑,但iOS 15 中调用attemptRotationToDeviceOrientation()无法主动触发旋转
解决方案
针对iOS 15及以下版本,需要主动更新顶层ViewController的支持方向,并结合系统API强制触发旋转。修改AppDelegate中的lockOrientation(to:)方法,确保在锁定方向后,系统能立即响应并旋转至目标方向。
修改后的核心代码
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, *) { // 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 { // iOS 15及以下:主动更新顶层VC的支持方向并触发旋转 if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, var topController = windowScene.windows.first?.rootViewController { while let presentedViewController = topController.presentedViewController { topController = presentedViewController } // 临时修改顶层VC的支持方向,确保系统能识别允许的方向 let originalSupportedOrientations = topController.supportedInterfaceOrientations topController.supportedInterfaceOrientations = orientation // 强制触发旋转 UIViewController.attemptRotationToDeviceOrientation() // 延迟恢复原有支持方向,避免影响后续逻辑 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { topController.supportedInterfaceOrientations = originalSupportedOrientations } } } } static func unlockOrientation() { Self.orientationLock = nil } }
补充说明
- 顶层ViewController获取:确保拿到当前显示的最上层ViewController,系统会优先参考顶层VC的方向设置
- 临时修改支持方向:iOS 15中仅通过AppDelegate设置方向锁可能无法被系统及时感知,临时修改顶层VC的
supportedInterfaceOrientations可以让系统立即识别允许的方向 - 延迟恢复设置:若VC原本有独立的方向逻辑,延迟恢复可避免影响后续的方向判断
修改后的完整测试代码
import SwiftUI import Combine @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 { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, var topController = windowScene.windows.first?.rootViewController { while let presentedViewController = topController.presentedViewController { topController = presentedViewController } let originalSupportedOrientations = topController.supportedInterfaceOrientations topController.supportedInterfaceOrientations = orientation UIViewController.attemptRotationToDeviceOrientation() DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { topController.supportedInterfaceOrientations = originalSupportedOrientations } } } } static func unlockOrientation() { Self.orientationLock = nil } }
内容的提问来源于stack exchange,提问作者Boyan Pavlov
相关产品推荐
相关产品推荐

