SwiftUI求助:如何让全屏弹窗视图展示前锁定为横屏?
问题:竖屏中展示横屏视图时先竖屏再切换的问题
我尝试从竖屏视图中展示一个横屏方向的视图,当前实现代码如下:
触发弹窗的代码
Button { isLandscapeViewPresented.toggle() } label: { Text("Click for landscape view") }.fullScreenCover(isPresented: $isLandscapeViewPresented) { LandscapeOnlyView { LandscapeView() } }
LandscapeOnlyView包装器代码
struct LandscapeOnlyView<Content: View>: UIViewControllerRepresentable { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } func makeUIViewController(context: Context) -> some UIViewController { return LandscapeHostingController(rootView: content) } }
LandscapeHostingController承载代码
class LandscapeHostingController<Content: View>: UIHostingController<Content> { override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .landscape } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) AppDelegate.orientationLock = .landscape let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene scene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight), errorHandler: { error in print(error) }) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) AppDelegate.orientationLock = .portrait let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene scene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait), errorHandler: { error in print(error) }) } }
我通过AppDelegate更新orientationLock,并重写supportedInterfaceOrientations返回所需方向。目前弹窗会先以竖屏展示,之后再切换为横屏。需要让横屏视图在展示前就处于横屏状态,而非展示后切换。
当前方案的问题
- 方向变更逻辑放在
viewWillAppear中,此时视图已经开始渲染,必然会先显示竖屏状态再切换,产生视觉延迟。 - 系统
fullScreenCover的默认逻辑是先以当前窗口方向加载内容,之后才响应方向变更,这是导致闪烁的核心原因。
可行实现思路
思路一:触发弹窗前提前切换方向
在按钮点击事件中,先完成方向锁设置和几何更新,等待方向切换完成后再弹出视图,确保弹窗加载时系统已经处于横屏状态:
Button { // 先设置全局方向锁 AppDelegate.orientationLock = .landscape guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { // 场景获取失败直接弹窗兜底 isLandscapeViewPresented.toggle() return } // 请求方向变更,完成后再弹窗 scene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight)) { error in print(error) DispatchQueue.main.async { self.isLandscapeViewPresented.toggle() } } } label: { Text("Click for landscape view") }
同时修改LandscapeHostingController,移除viewWillAppear中的重复方向设置,只保留viewWillDisappear的恢复逻辑即可。
思路二:自定义全屏弹窗容器
绕过系统fullScreenCover,直接用UIKit自定义全屏弹窗控制器,在初始化阶段就指定支持的方向,确保展示前方向已切换:
// 自定义横屏全屏弹窗控制器 class LandscapeFullScreenController: UIViewController { private let contentView: UIView init(content: UIView) { self.contentView = content super.init(nibName: nil, bundle: nil) modalPresentationStyle = .fullScreen } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .landscape } override func viewDidLoad() { super.viewDidLoad() view.addSubview(contentView) contentView.frame = view.bounds contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] } } // SwiftUI中调用 Button { // 先设置方向锁 AppDelegate.orientationLock = .landscape guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return } let hostingView = UIHostingController(rootView: LandscapeView()).view! let landscapeController = LandscapeFullScreenController(content: hostingView) scene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight)) { [weak self] error in print(error) DispatchQueue.main.async { self?.present(landscapeController, animated: true) } } } label: { Text("Click for landscape view") }
思路三:利用展示代理提前配置方向
通过UIAdaptivePresentationControllerDelegate,在弹窗即将展示时就触发方向变更,避免在视图已开始渲染后再操作:
struct LandscapeOnlyView<Content: View>: UIViewControllerRepresentable { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } func makeUIViewController(context: Context) -> some UIViewController { let hostingController = LandscapeHostingController(rootView: content) // 设置展示代理 hostingController.presentationController?.delegate = context.coordinator return hostingController } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate { func presentationControllerWillPresent(_ presentationController: UIPresentationController) { // 即将展示时设置方向 AppDelegate.orientationLock = .landscape if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { scene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight)) { error in print(error) } } } } }
同时修改LandscapeHostingController,移除viewWillAppear中的方向设置代码,避免重复操作。
内容的提问来源于stack exchange,提问作者vishnu_146
相关产品推荐
相关产品推荐

