You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 13:30:43