You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

补充说明

  1. 顶层ViewController获取:确保拿到当前显示的最上层ViewController,系统会优先参考顶层VC的方向设置
  2. 临时修改支持方向:iOS 15中仅通过AppDelegate设置方向锁可能无法被系统及时感知,临时修改顶层VC的supportedInterfaceOrientations可以让系统立即识别允许的方向
  3. 延迟恢复设置:若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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 00:25:26