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

iPad应用旋转时因系统密码保存弹窗引发崩溃的解决求助

解决方案:iPad密码保存弹窗旋转崩溃问题

崩溃核心原因是:旋转设备时,系统的密码保存相关VC(_SFAppPasswordSavingViewController、UIKeyboardHiddenViewController_Save)尝试重复present,而此时原有present关系未被正确清理,导致系统抛出NSInvalidArgumentException。

以下是几个可行的修复方案:

方案一:递归dismiss所有层级的Modal VC

你的replaceRootViewControllerWith方法仅dismiss了rootVC直接present的VC,但系统密码弹窗可能处于更深的present层级。修改逻辑,递归dismiss所有modal VC:

  1. 给UIViewController添加递归dismiss的扩展:
extension UIViewController {
    func dismissAllPresented(completion: (() -> Void)?) {
        if let presentedVC = presentedViewController {
            presentedVC.dismissAllPresented { [weak self] in
                self?.dismiss(animated: false, completion: completion)
            }
        } else {
            completion?()
        }
    }
}
  1. 更新replaceRootViewControllerWith中的dismiss逻辑:
DispatchQueue.main.async { [weak self] in
    if let rootVC = self?.rootViewController {
        rootVC.dismissAllPresented(completion: dismissCompletion)
    } else {
        dismissCompletion()
    }
}

此方案确保所有层级的modal(包括系统的密码弹窗)都被彻底dismiss,避免root替换时与系统弹窗产生冲突。

方案二:旋转前主动关闭系统密码弹窗

在设备旋转前,检测并关闭系统的密码保存弹窗:

  1. 添加VC扩展用于查找系统密码弹窗:
extension UIViewController {
    func findSystemPasswordVC() -> UIViewController? {
        var currentVC = self.presentedViewController
        while currentVC != nil {
            let className = String(describing: type(of: currentVC!))
            if className.contains("_SFAppPasswordSaving") || className.contains("UIKeyboardHiddenViewController_Save") {
                return currentVC
            }
            currentVC = currentVC?.presentedViewController
        }
        return nil
    }
}
  1. 在登录VC中监听旋转事件:
class LoginVC: UIViewController {
    override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
        super.willRotate(to: toInterfaceOrientation, duration: duration)
        // 查找并关闭系统密码弹窗
        if let systemVC = UIApplication.shared.keyWindow?.rootViewController?.findSystemPasswordVC() {
            systemVC.dismiss(animated: false, completion: nil)
        }
    }
}

此方案在旋转触发前清理系统弹窗,从源头避免重复present的冲突。

方案三:Hook系统present方法避免重复弹窗(谨慎使用)

通过方法交换(Swizzling)拦截系统的present逻辑,当检测到是密码保存VC时,避免重复present:

  1. 添加UIViewController的Swizzling扩展:
extension UIViewController {
    static func setupPresentSwizzling() {
        let originalSelector = #selector(present(_:animated:completion:))
        let swizzledSelector = #selector(swizzledPresent(_:animated:completion:))
        
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else { return }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
    
    @objc private func swizzledPresent(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        let vcClassName = String(describing: type(of: viewControllerToPresent))
        if vcClassName.contains("_SFAppPasswordSavingViewController") {
            // 检查是否已存在该类型的弹窗
            var currentVC = self
            while let presented = currentVC.presentedViewController {
                if String(describing: type(of: presented)).contains("_SFAppPasswordSavingViewController") {
                    completion?()
                    return
                }
                currentVC = presented
            }
        }
        // 调用原present方法
        swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
    }
}
  1. 在AppDelegate中初始化Swizzling:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UIViewController.setupPresentSwizzling()
    return true
}

⚠️ 注意:方法交换属于黑魔法,可能与系统后续版本的API变更冲突,使用前需充分测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 17:40:32