Unwind Segue跳转至未调用视图控制器及启动屏逻辑技术咨询
我来帮你解决这个Unwind Segue跳转的问题,首先得理清你的场景核心矛盾:当用户已登录时,启动屏直接跳转到首页B,此时登录页A并没有被加入到导航栈中——而Unwind Segue的本质是沿着当前导航栈向上查找目标视图控制器,如果目标VC不在栈里,常规Unwind就会失效。下面分步骤给你完整解决方案:
你现在的代码里逻辑搞反了:当检测到已登录用户(if let user = user)时,却调用了跳转到登录屏的方法,这完全不对。先把这块代码修正为正确的逻辑:
Auth.auth().addStateDidChangeListener { auth, user in // 确保在主线程执行UI操作 DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if let user = user { // 已登录:跳转到首页B let homeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController // 替换当前导航栈为首页,避免启动屏留在栈中 self.navigationController?.setViewControllers([homeVC], animated: true) // 如果是用window根视图控制器的方式(比如启动屏是根VC): // let navVC = UINavigationController(rootViewController: homeVC) // UIApplication.shared.keyWindow?.rootViewController = navVC } else { // 未登录:跳转到登录页A let loginVC = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController self.navigationController?.setViewControllers([loginVC], animated: true) } } }
这里分两种方案,你可以根据需求选择:
方案一:结合Unwind Segue和根VC重置(覆盖所有场景)
这个方案既能处理用户从A→B→C的常规路径(此时A在栈中),也能处理启动屏直接→B→C的路径(此时A不在栈中):
在登录页A(LoginViewController)中定义Unwind目标方法:
这个方法是Unwind Segue的“锚点”,系统会沿着栈向上找有这个方法的VC:@IBAction func unwindToLogin(segue: UIStoryboardSegue) { // 在这里做注销后的清理工作,比如清除本地缓存的登录信息 UserDefaults.standard.removeObject(forKey: "userAuthToken") try? Auth.auth().signOut() }在Storyboard中配置Unwind Segue:
打开Storyboard,找到设置页C的注销按钮,按住Ctrl键拖动按钮到C视图控制器顶部的Exit图标(就是那个绿色的退出箭头),在弹出的选项中选择你刚才定义的unwindToLogin方法,然后给这个Segue设置一个标识符(比如unwindToLogin)。在设置页C中编写注销按钮的点击逻辑:
点击按钮时,先检查导航栈里是否存在A,如果存在就用Unwind,不存在就直接重置根VC:@IBAction func logoutButtonTapped(_ sender: UIButton) { // 先执行注销操作 try? Auth.auth().signOut() UserDefaults.standard.removeObject(forKey: "userAuthToken") // 检查导航栈中是否有登录页A if navigationController?.viewControllers.contains(where: { $0 is LoginViewController }) == true { // 存在则执行Unwind Segue performSegue(withIdentifier: "unwindToLogin", sender: self) } else { // 不存在则直接重置根视图控制器为A let storyboard = UIStoryboard(name: "Main", bundle: nil) let loginVC = storyboard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController let navController = UINavigationController(rootViewController: loginVC) // 适配iOS 13+的window获取方式 if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = scene.windows.first { UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: { window.rootViewController = navController }, completion: nil) window.makeKeyAndVisible() } } }
方案二:直接重置根视图控制器(更简洁)
其实不管导航栈里有没有A,直接把根VC换成登录页A是更简单的方案,不需要配置Unwind Segue,逻辑更清晰:
@IBAction func logoutButtonTapped(_ sender: UIButton) { // 执行注销操作 try? Auth.auth().signOut() UserDefaults.standard.removeObject(forKey: "userAuthToken") // 实例化登录页A并设置为根VC let storyboard = UIStoryboard(name: "Main", bundle: nil) let loginVC = storyboard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController let navController = UINavigationController(rootViewController: loginVC) // 添加过渡动画,提升用户体验 if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = scene.windows.first { UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: { window.rootViewController = navController }, completion: nil) window.makeKeyAndVisible() } }
Unwind Segue的工作逻辑是从当前VC开始,沿着导航栈向上遍历,找到第一个包含匹配Unwind方法的VC,然后把栈中当前VC到目标VC之间的所有VC都弹出,回到目标VC。如果目标VC(A)不在当前导航栈里,系统找不到对应的VC,Unwind操作就不会执行——这就是你遇到问题的核心原因。
内容的提问来源于stack exchange,提问作者Mido2009

