iPad应用旋转时因系统密码保存弹窗引发崩溃的解决求助
解决方案:iPad密码保存弹窗旋转崩溃问题
崩溃核心原因是:旋转设备时,系统的密码保存相关VC(_SFAppPasswordSavingViewController、UIKeyboardHiddenViewController_Save)尝试重复present,而此时原有present关系未被正确清理,导致系统抛出NSInvalidArgumentException。
以下是几个可行的修复方案:
方案一:递归dismiss所有层级的Modal VC
你的replaceRootViewControllerWith方法仅dismiss了rootVC直接present的VC,但系统密码弹窗可能处于更深的present层级。修改逻辑,递归dismiss所有modal VC:
- 给
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?() } } }
- 更新
replaceRootViewControllerWith中的dismiss逻辑:
DispatchQueue.main.async { [weak self] in if let rootVC = self?.rootViewController { rootVC.dismissAllPresented(completion: dismissCompletion) } else { dismissCompletion() } }
此方案确保所有层级的modal(包括系统的密码弹窗)都被彻底dismiss,避免root替换时与系统弹窗产生冲突。
方案二:旋转前主动关闭系统密码弹窗
在设备旋转前,检测并关闭系统的密码保存弹窗:
- 添加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 } }
- 在登录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:
- 添加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) } }
- 在AppDelegate中初始化Swizzling:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UIViewController.setupPresentSwizzling() return true }
⚠️ 注意:方法交换属于黑魔法,可能与系统后续版本的API变更冲突,使用前需充分测试。
内容的提问来源于stack exchange,提问作者manish_kumar
相关产品推荐
相关产品推荐

