使用BlackBerry Dynamics SDK时iOS 13应用如何访问rootViewController?
嘿,我碰到过类似的SDK接管window层级的坑,这就帮你理清楚问题出在哪以及怎么修复!
问题到底出在哪?
BlackBerry Dynamics SDK的GDiOS.sharedInstance().authorize()方法会完全接管你的应用window层级——它会创建自己的内部UIViewController作为root,用来处理身份验证、安全容器初始化这些流程。这就是为什么你在release模式下看到的root不是你设置的HomeController,而是一个陌生的UIViewController,自然会触发"view is not in the window hierarchy"的错误,因为这个SDK创建的控制器并没有在当前显示的window层级里。
另外你原来的代码在debug模式下手动创建window并设置root,但release模式下依赖storyboard的初始控制器,同时SDK的授权操作会干扰这个初始化流程,进一步搞乱了界面层级。
正确的处理方案
1. 等SDK授权完成后再设置你的根控制器
BlackBerry Dynamics会通过代理回调通知你授权完成,你要在这个时机再设置自己的根控制器,确保SDK初始化完后再接管界面:
首先完善你的XXXGDiOSDelegate,实现授权成功的回调(具体方法名参考SDK文档,通常是didAuthorize):
class XXXGDiOSDelegate: NSObject, GDiOSDelegate { static let sharedInstance = XXXGDiOSDelegate() weak var appDelegate: AppDelegate? func didAuthorize(_ error: Error?) { guard error == nil else { // 这里处理授权失败的逻辑,比如弹提示 print("BlackBerry授权失败: \(error!)") return } // 授权成功,设置我们自己的根控制器 let storyboard = UIStoryboard(name: "Main", bundle: nil) let homeController = storyboard.instantiateViewController(withIdentifier: "HomeController") as! HomeController // 获取SDK管理的window(此时window已经被SDK初始化了) if let window = UIApplication.shared.windows.first { window.rootViewController = homeController window.makeKeyAndVisible() } // 初始化那些需要保留状态的ViewController,存在AppDelegate里 appDelegate?.setupPersistentViewControllers() } }
然后修改AppDelegate的didFinishLaunchingWithOptions,只负责初始化SDK,不再直接设置root:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 先初始化BlackBerry Dynamics的代理和授权 XXXGDiOSDelegate.sharedInstance.appDelegate = self GDiOS.sharedInstance().authorize(XXXGDiOSDelegate.sharedInstance) // Debug模式下跳过SDK授权,直接设置根控制器 #if DEBUG let storyboard = UIStoryboard(name: "Main", bundle: nil) let homeController = storyboard.instantiateViewController(withIdentifier: "HomeController") as! HomeController window = UIWindow() window?.rootViewController = homeController window?.makeKeyAndVisible() setupPersistentViewControllers() #endif return true } // 单独抽方法初始化需要保留状态的VC func setupPersistentViewControllers() { let storyboard = UIStoryboard(name: "Main", bundle: nil) // 这里把你需要保留状态的VC都初始化好存在AppDelegate的属性里 uploadController = storyboard.instantiateViewController(withIdentifier: "UploadController") as! UploadController // 比如其他VC:profileController = ... 以此类推 } // 修改getViewController方法,直接返回预初始化的实例 func getViewController(viewController: VCType) -> UIViewController { switch viewController { case .Uploads: return uploadController! // 其他VC的case补充在这里 } }
2. 跳转时确保拿到正确的活跃控制器
在你的跳转代码里,不要硬怼UIApplication.shared.windows.first!.rootViewController,而是先确认这个控制器确实在window层级里,或者用当前显示的控制器来present:
@objc func onMenuItemUploadsTap(_ sender: UIView) { self.dismiss(animated: false) { [weak self] in guard let self = self else { return } let appDelegate = UIApplication.shared.delegate as! AppDelegate let vc = appDelegate.getViewController(viewController: .Uploads) vc.modalPresentationStyle = .fullScreen // 先尝试用有效的root控制器present if let rootVC = UIApplication.shared.windows.first?.rootViewController, rootVC.view.window != nil { rootVC.present(vc, animated: true) } else { // fallback:如果root不可用,用当前控制器来present self.present(vc, animated: true) } } }
几个额外的注意点
- 别再依赖storyboard的"Is Initial View Controller"设置了,SDK会直接覆盖它,手动用代码设置根控制器更可靠。
- 所有需要保留状态的ViewController必须在SDK授权成功后再初始化,这样它们的生命周期才会在SDK的安全容器环境下正常运行。
- 测试release模式时,务必检查SDK的配置(比如entitlements文件、Provisioning Profile)是否正确,很多时候SDK初始化失败也会导致界面异常。
内容的提问来源于stack exchange,提问作者Schnemar

