如何在UIHostingController的SwiftUI视图中调用pushViewController跳转
在SwiftUI视图中调用UIKit ViewController的导航方法
问题描述
我有一个UIKit应用,通过UIHostingController展示SwiftUI视图。该视图内有一个按钮,希望点击它导航至另一个ViewController。请问如何在此SwiftUI视图内部push新的ViewController?(或更宽泛地:如何从SwiftUI视图内部调用ViewController的函数?)
当然我可以在SwiftUI中使用NavigationView,但为了实现业务逻辑与UI的清晰分离,我希望大部分逻辑用UIKit实现。
原始代码如下:
MainViewController代码
import UIKit import SwiftUI class MainViewController: UIViewController { var viewModel = ViewModel() override func viewDidLoad() { super.viewDidLoad() addView() } func addView() { let testView = SwiftUIView(viewModel: viewModel) let controller = UIHostingController(rootView: testView) addChild(controller) controller.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(controller.view) controller.didMove(toParent: self) NSLayoutConstraint.activate([ controller.view.widthAnchor.constraint(equalTo: view.widthAnchor), controller.view.heightAnchor.constraint(equalTo: view.heightAnchor), controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor), controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } func navigate() { // 如何从SwiftUI视图中调用这个方法? let otherView = Text("test") let nextVc = UIHostingController(rootView: otherView) navigationController?.pushViewController(nextVc, animated: true) } }
SwiftUI视图代码
import SwiftUI struct SwiftUIView: View { @ObservedObject var viewModel: ViewModel var body: some View { Button { // 在这里触发push操作 } label: { Text("go") } } }
解决方案
方法一:通过ViewModel传递闭包(推荐)
利用ViewModel作为中间层,在ViewModel中定义闭包属性,将UIKit的导航逻辑绑定到闭包上,SwiftUI视图只需调用该闭包即可触发导航。这种方式既保持了UI与业务逻辑的分离,又实现了跨框架的交互。
步骤1:更新ViewModel
添加用于触发导航的闭包属性:
import Foundation class ViewModel: ObservableObject { // 定义导航闭包,可选类型避免空值问题 var navigateAction: (() -> Void)? }
步骤2:在MainViewController中绑定闭包
在viewDidLoad中为ViewModel的闭包赋值,关联到navigate方法:
override func viewDidLoad() { super.viewDidLoad() addView() // 绑定导航逻辑到ViewModel的闭包,使用weak self避免循环引用 viewModel.navigateAction = { [weak self] in self?.navigate() } }
步骤3:在SwiftUI视图中调用闭包
修改按钮的点击事件,调用ViewModel中的闭包:
struct SwiftUIView: View { @ObservedObject var viewModel: ViewModel var body: some View { Button { // 调用闭包触发导航 viewModel.navigateAction?() } label: { Text("go") } } }
方法二:直接传递ViewController引用(不推荐)
如果不需要严格的解耦,可以直接将MainViewController的弱引用传递给SwiftUI视图,直接调用其方法。但这种方式会增加SwiftUI与UIKit的耦合度,不利于后续维护。
步骤1:修改SwiftUI视图,添加ViewController引用
struct SwiftUIView: View { @ObservedObject var viewModel: ViewModel // 使用weak引用避免循环引用 weak var parentViewController: MainViewController? var body: some View { Button { parentViewController?.navigate() } label: { Text("go") } } }
步骤2:在MainViewController中传递引用
func addView() { // 创建SwiftUI视图时传入当前ViewController的引用 let testView = SwiftUIView(viewModel: viewModel, parentViewController: self) let controller = UIHostingController(rootView: testView) // ... 其余代码不变 }
总结
方法一通过ViewModel传递闭包,符合MVVM设计思想,实现了低耦合,是更优的方案;方法二更直接,但耦合度高,仅适用于简单场景。
内容的提问来源于stack exchange,提问作者ambassador
相关产品推荐
相关产品推荐

