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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:49:57