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

Swift使用present配合fullScreen触发不平衡转场调用错误咨询

问题解答

报错原因说明

.fullScreen 模态样式转场完成后自动移除底层presenting视图的内置行为不是触发unbalanced calls to begin/end appearance transitions警告的根本原因。
这个警告的本质是UIKit检测到视图控制器的生命周期转场调用不成对:也就是某一轮viewWillAppear/viewDidAppear、viewWillDisappear/viewDidDisappear的调用序列被打断,通常是因为你在当前控制器还没完成自身加载/上一轮转场时,就触发了新的模态弹出操作。
你遇到的场景属于典型的启动时序问题:应用启动时mainView作为根控制器还没走完自身的viewDidAppear流程,你就立刻调用present弹出loginView。.fullScreen样式触发弹出时会立刻给底层的mainView发送viewWillDisappear事件,此时mainView自己的viewDidAppear还没执行完,就会出现转场调用不匹配的警告。而.overFullScreen样式不会触发底层视图的disappear生命周期,所以不会撞上这个时序问题,这也是换样式后警告消失的原因。
另外你当前的topViewController获取逻辑在iOS13+多场景环境下有缺陷:启动阶段UIApplication.shared.keyWindow可能还没完成挂载,拿到的控制器状态不正确,也会加剧转场时序问题。

可行解决方案

按优先级推荐以下方案,都可以在保留.fullScreen样式的前提下消除警告:

  • 方案1:调整present调用时机,等控制器处于空闲状态再执行弹出
    不要在根控制器的viewDidLoad、viewWillAppear方法里直接调用present,把弹出逻辑放到viewDidAppear中执行;如果是启动即弹的场景,可以把present逻辑派发到主线程下一轮循环执行,等当前启动转场全部完成后再触发:
    DispatchQueue.main.async {
        // 此时主线程上排队的启动阶段转场任务已经全部执行完
        presentView(viewInstance: loginView)
    }
    // 极端场景下如果还有警告,可以加0.1s的延迟兜底
    // DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    //     presentView(viewInstance: loginView)
    // }
    
  • 方案2:修正topViewController的获取逻辑,适配iOS13+多场景规则,避免拿到状态异常的控制器
    替换你现有的UIApplication扩展为以下实现:
    extension UIApplication {
        class func topViewController(controller: UIViewController? = nil) -> UIViewController? {
            let rootController = controller ?? {
                if #available(iOS 13.0, *) {
                    let activeScene = connectedScenes
                        .filter { $0.activationState == .foregroundActive }
                        .first as? UIWindowScene
                    return activeScene?.windows.first(where: \.isKeyWindow)?.rootViewController
                } else {
                    return keyWindow?.rootViewController
                }
            }()
            guard let root = rootController else { return nil }
            if let nav = root as? UINavigationController {
                return topViewController(controller: nav.visibleViewController)
            }
            if let tab = root as? UITabBarController, let selected = tab.selectedViewController {
                return topViewController(controller: selected)
            }
            if let presented = root.presentedViewController {
                return topViewController(controller: presented)
            }
            return root
        }
    }
    
  • 方案3:启动阶段切换首个视图直接替换rootViewController,避免模态转场的时序问题
    如果loginView是启动后要显示的首个页面,完全不需要用present的方式弹出,直接替换window的根控制器即可,性能更高也不会有转场冲突:
    guard let window = UIApplication.shared.windows.first(where: \.isKeyWindow) else { return }
    window.rootViewController = loginView
    UIView.transition(with: window, duration: 0.2, options: .transitionCrossDissolve, animations: nil)
    

验证小技巧

调用present前可以打印当前topController的几个状态属性,如果任意一个属性为true,说明当前控制器还在转场流程中,此时调用present必然会触发警告,等所有属性都为false时再调用即可:

print(topController.isBeingPresented, topController.isBeingDismissed, topController.isMovingToParent, topController.isMovingFromParent)

内容的提问来源于stack exchange,提问作者Bobby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 06:12:59