如何在SwiftUI ViewModel中触发Storyboard的Segue跳转
问题
我正在将SwiftUI视图及ViewModel注入Storyboard,为后续应用重构为SwiftUI减少工作量。创建了LoginViewHostingController来注入SwiftUI视图:
class LoginViewHostingController: UIHostingController<LoginView> { required init?(coder: NSCoder) { super.init(coder: coder,rootView: LoginView()) } override func viewDidLoad() { super.viewDidLoad() } }
LoginView通过@ObservedObject引用LoginViewModel处理状态逻辑,按钮点击时调用loginVM.checkEmailForSSOAuthentication方法,希望在此方法中触发当前Storyboard的Segue跳转,尝试的代码如下:
struct LoginView: View { @ObservedObject var loginVM = LoginViewModel() var body: some View { // ... if (!loginVM.showPasswordView) { HStack { Button(action: loginVM.checkEmailForSSOAuthentication) { Text("Next") .font(.system(.headline, design: .rounded)) .foregroundColor(Color.white) .padding(.vertical, 18) .frame(width: 350) .background(Color("DigideckPrimary")) .cornerRadius(6) } } .padding(.top, 10) } } }
ViewModel中的尝试代码:
func checkEmailForSSOAuthentication() { let storyboard = UIStoryboard(name: "Main", bundle: nil) let storyBoardLoginVc = storyboard.instantiateViewController(withIdentifier: "myLoginController") // instead of sending to self send to main storyboard if i can! storyBoardLoginVc.performSegue(withIdentifier: "test1", sender: self) return; }
执行时出现错误:
2023-01-18 09:47:00.872611-0600 Digideck[35908:12697138] [Assert] UINavigationBar decoded as unlocked for UINavigationController, or navigationBar delegate set up incorrectly. Inconsistent configuration may cause problems. navigationController=<UINavigationController: 0x130879c00>, navigationBar=<UINavigationBar: 0x127e1ce70; frame = (0 0; 0 50); opaque = NO; autoresize = W; tintColor = UIExtendedSRGBColorSpace 0.0784314 0.392157 0.709804 1; gestureRecognizers = <NSArray: 0x6000010ab090>; layer = <CALayer: 0x600001ec0ea0>> delegate=0x130879c00 2023-01-18 09:47:00.873019-0600 Digideck[35908:12697138] [Presentation] Attempt to present <UINavigationController: 0x130879c00> on <Digideck.LoginViewHostingController: 0x10f00b600> (from <Digideck.LoginViewHostingController: 0x10f00b600>) whose view is not in the window hierarchy.
推测是实例化了新的Storyboard而非引用当前视图所在的Storyboard,请问如何实现该需求?
解决方案
核心问题是ViewModel无法直接访问当前所在的ViewController上下文,且新实例化的ViewController未加入视图层级,导致Segue无法触发。可以通过以下方式解决:
1. 用回调让宿主ViewController执行Segue
让ViewModel专注业务逻辑,通过闭包回调通知LoginViewHostingController执行跳转操作,符合MVVM架构原则。
修改LoginViewModel
添加可选闭包用于触发跳转回调:
class LoginViewModel: ObservableObject { @Published var showPasswordView = false // 跳转回调闭包 var onTriggerSegue: (() -> Void)? func checkEmailForSSOAuthentication() { // 这里保留原有业务逻辑(比如邮箱验证等) // 逻辑验证通过后,触发跳转回调 onTriggerSegue?() } }
修改LoginView
调整初始化方法,接收宿主传递的Segue触发逻辑并绑定到ViewModel:
struct LoginView: View { @ObservedObject var loginVM: LoginViewModel private let triggerSegue: () -> Void init(triggerSegue: @escaping () -> Void) { self.triggerSegue = triggerSegue self.loginVM = LoginViewModel() // 将跳转逻辑绑定到ViewModel的回调 loginVM.onTriggerSegue = triggerSegue } var body: some View { // 原有视图代码保持不变 if (!loginVM.showPasswordView) { HStack { Button(action: loginVM.checkEmailForSSOAuthentication) { Text("Next") .font(.system(.headline, design: .rounded)) .foregroundColor(Color.white) .padding(.vertical, 18) .frame(width: 350) .background(Color("DigideckPrimary")) .cornerRadius(6) } } .padding(.top, 10) } } }
修改LoginViewHostingController
初始化时传递Segue触发方法,直接调用自身的performSegue:
class LoginViewHostingController: UIHostingController<LoginView> { required init?(coder: NSCoder) { let rootView = LoginView { [weak self] in // 触发当前ViewController的Segue self?.performSegue(withIdentifier: "test1", sender: self) } super.init(coder: coder, rootView: rootView) } override func viewDidLoad() { super.viewDidLoad() } }
2. 注意事项
- 使用
[weak self]避免循环引用,防止内存泄漏。 - 确保Storyboard中已正确设置Segue标识符
test1,且LoginViewHostingController所在的ViewController已处于视图层级中。 - 不要让ViewModel直接依赖UIKit组件,保持业务逻辑与界面逻辑分离。
内容的提问来源于stack exchange,提问作者NickKnack13
相关产品推荐
相关产品推荐

