Swift开发:如何禁止未认证用户导航返回根viewController
问题根源
你当前实现的核心问题是:无论用push还是模态方式弹出登录页,受认证保护的根主控制器始终存在于导航栈/视图层级中,只要视图层级里保留了主控制器实例,就始终存在被返回手势、导航按钮带回主页面的可能。
iOS完全支持类似Androidfinish()销毁当前控制器实例、直接切换应用根视图的操作,这也是处理全局登录鉴权的标准实现方案,完全不需要在主控制器加载后再叠加弹出登录页。
最优方案:直接切换Window根控制器
这个方案会在未登录状态下完全不初始化主控制器实例,从根源上杜绝未认证用户访问主页面的可能,没有任何返回路径漏洞。
实现步骤
- 第一步:把登录态校验逻辑从主控制器的
viewDidLoad前移到应用启动入口
不要等主控制器加载完成再判断登录状态,在iOS13+的SceneDelegate、或低版本的AppDelegate启动方法中完成校验,直接初始化对应状态的根控制器,示例代码:// SceneDelegate中实现,AppDelegate逻辑完全一致 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) // 提前校验登录状态 if isUserSignedIn() { // 已登录:初始化主界面作为根控制器 let mainVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainRootVC") window.rootViewController = UINavigationController(rootViewController: mainVC) } else { // 未登录:初始化登录流程作为根控制器 let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") window.rootViewController = UINavigationController(rootViewController: loginVC) } self.window = window window.makeKeyAndVisible() } - 第二步:登录态变更时全局切换根控制器
不管是用户登录成功、主动登出,都不需要做push/present跳转,直接重新设置window的根控制器即可,旧的控制器实例会被系统自动回收,和Android调用finish()销毁页面的效果完全一致,可以加简单的淡入淡出动画避免切换生硬:// 登录成功/登出时全局调用 func updateRootControllerForLoginState(isSignedIn: Bool) { guard let window = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .compactMap({$0 as? UIWindowScene}) .first?.windows.first(where: {$0.isKeyWindow}) else { return } let targetVC: UIViewController if isSignedIn { let mainVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainRootVC") targetVC = UINavigationController(rootViewController: mainVC) } else { let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") targetVC = UINavigationController(rootViewController: loginVC) } UIView.transition(with: window, duration: 0.25, options: .transitionCrossDissolve) { window.rootViewController = targetVC } }
临时兼容方案:修复现有跳转的返回漏洞
如果暂时不想调整整体结构,只想堵住现有实现的返回路径,可以做如下修改:
- 把跳转逻辑从
viewDidLoad移到viewDidAppear:viewDidLoad执行时控制器视图还没完成挂载,此时执行跳转很容易出现弹窗失败、界面错乱问题。 - 如果用模态弹出:设置登录页
isModalInPresentation = true禁止侧滑dismiss,隐藏返回按钮,并且用全屏模态样式:override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) guard !isUserSignedIn() else { return } let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") loginVC.isModalInPresentation = true loginVC.navigationItem.hidesBackButton = true let loginNav = UINavigationController(rootViewController: loginVC) loginNav.modalPresentationStyle = .fullScreen present(loginNav, animated: true) } - 如果用push跳转:隐藏导航返回按钮,同时禁用导航控制器的交互式侧滑pop手势:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationItem.hidesBackButton = true navigationController?.interactivePopGestureRecognizer?.isEnabled = false } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 离开页面时恢复手势,避免影响其他页面交互 navigationController?.interactivePopGestureRecognizer?.isEnabled = true }
注意:这个补丁方案只是堵住了现有交互的返回入口,主控制器实例会一直驻留在内存中,存在流程上的漏洞,长期维护的项目优先选择切换根控制器的方案。
内容的提问来源于stack exchange,提问作者Dylon Jaynes
相关产品推荐
相关产品推荐

