iOS 获取 Presented View Controller 的最佳实践
2024-11-03 13:36:42
深层链接跳转与获取 Presented View Controller
在 App 开发中,我们经常需要通过深层链接跳转到特定页面。这听起来很简单,但如果应用的层级比较复杂,例如包含 TabBarController、NavigationController 和模态视图,要准确获取当前显示的视图控制器(尤其是模态视图),就可能会遇到一些挑战。 开发者经常会遇到类似 "Reference currently presented view controller" 的问题。本文将分享一些获取 presented view controller 的最佳实践,帮你轻松解决这个难题。
问题场景与分析
想象一下这样的场景:你的应用有一个 UITabBarController 作为根视图,其中第一个 Tab 包含一个 DashboardViewController。当用户未登录时,DashboardViewController 会模态地 present 一个 LoginViewController。现在,你希望通过深层链接直接跳转到应用内的某个页面。如果用户已经登录,一切顺利。但如果用户未登录,应用还在显示 LoginViewController,该如何处理呢?这时候就需要准确获取当前 presented 的 LoginViewController,才能在其上进行操作。
开发者提供了一种常见的获取顶层视图控制器的方法,但该方法在处理模态视图时遇到了问题,只能获取到 TabBarController 中的 DashboardViewController,而无法获取到 presented 的 LoginViewController。这是因为该方法的递归逻辑在遇到 TabBarController 后就停止了,没有进一步查找 presentedViewController。
解决方案一:改进递归查找方法
我们可以改进原有的递归查找方法,使其能够正确处理 TabBarController 和 presentedViewController 的组合。关键在于在检查 selectedViewController 的同时,也要检查其 presentedViewController。
func getTopViewController() -> UIViewController? {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first else {
return nil
}
return topViewControllerWithRootViewController(window.rootViewController)
}
func topViewControllerWithRootViewController(rootViewController: UIViewController?) -> UIViewController? {
guard let rootViewController = rootViewController else { return nil }
if let tabBarController = rootViewController as? UITabBarController,
let selectedVC = tabBarController.selectedViewController {
return topViewControllerWithRootViewController(selectedVC.presentedViewController ?? selectedVC)
}
if let navigationController = rootViewController as? UINavigationController {
return topViewControllerWithRootViewController(navigationController.visibleViewController)
}
if let presentedViewController = rootViewController.presentedViewController {
return topViewControllerWithRootViewController(presentedViewController)
}
return rootViewController
}
操作步骤:
- 将上述代码添加到你的项目中。
- 调用
getTopViewController()
方法获取最顶层的视图控制器。
这个改进的版本通过优先查找 selectedViewController 的 presentedViewController 解决了之前的问题。这个方法对你有帮助吗?
解决方案二:使用 NotificationCenter 监听 presentedViewController 的变化
另一种方法是使用 NotificationCenter
监听 UIViewController.didPresentNotification
通知,这样就可以在每次 present 新的视图控制器时得到通知,并更新当前顶层视图控制器的引用。
// 在 AppDelegate 或 SceneDelegate 中注册监听
NotificationCenter.default.addObserver(forName: UIViewController.didPresentNotification, object: nil, queue: .main) { [weak self] notification in
guard let presentedViewController = (notification.object as? UIViewController)?.presentedViewController else { return }
// 更新你的顶层视图控制器引用
self?.topViewController = presentedViewController
}
// 在需要使用的地方访问 topViewController
if let topVC = topViewController {
// 对 topVC 进行操作
}
操作步骤:
- 在
AppDelegate
或SceneDelegate
中的application(_:didFinishLaunchingWithOptions:)
或scene(_:willConnectTo:options:)
方法中添加监听代码。 - 创建一个属性来存储顶层视图控制器
var topViewController: UIViewController?
- 在通知回调中更新
topViewController
属性。
这个方法可以实时追踪 presentedViewController 的变化,但需要注意避免循环引用,因此在闭包中使用了 [weak self]
。你还有其他更好的建议吗?
额外的安全建议
- 在使用获取到的 presentedViewController 之前,最好进行类型检查,确保它是预期的类型。
- 避免在异步操作中直接使用获取到的 presentedViewController,因为视图层级可能会发生变化,导致引用失效。可以考虑使用 DispatchQueue.main.async 来确保在主线程更新 UI。
相关资源
- Apple 官方文档: UIViewController
- Apple 官方文档: UITabBarController
- Apple 官方文档: UINavigationController
希望本文能帮助你解决 "Reference currently presented view controller" 的问题,并提供了一些在复杂视图层级中获取视图控制器的实用技巧。 选择适合你的方法,并根据你的应用场景进行调整。记住,理解视图控制器的生命周期和层级关系至关重要!