如何在UIKit中正确调用SwiftUI视图的方法?
问题:UIKit调用SwiftUI方法无法获取最新状态值
在UIViewController中通过UIHostingController嵌入SwiftUI视图,SwiftUI视图包含表单。点击SwiftUI内部的按钮能正常修改状态,但点击UIKit导航栏的按钮调用SwiftUI方法时,无法获取修改后的状态值,始终是初始值。
UIViewController代码
import UIKit import SwiftUI class TestVC: UIViewController { var testView1: TestView1? override func viewDidLoad() { super.viewDidLoad() self.setupData() self.setupUI() } func setupData() { } func setupUI() { self.navigationController?.navigationBar.isTranslucent = false setupNavBar() let contentView = TestView1() let hostVC = UIHostingController(rootView: contentView) guard let hostView = hostVC.view else { return } view.addSubview(hostView) hostView.frame = self.view.frame self.testView1 = contentView } func setupNavBar() { let item1 = UIBarButtonItem(customView: self.runBtn) navigationItem.rightBarButtonItems = [item1] } lazy var runBtn: UIButton = { let btn = UIButton() btn.setTitle("Run", for: .normal) btn.setTitleColor(.blue, for: .normal) btn.addTarget(self, action: #selector(runBtnAction), for: .touchUpInside) return btn }() @objc func runBtnAction() { self.testView1?.run2() } }
SwiftUI视图代码
import SwiftUI struct TestView1: View { @State private var inputContent: String = "init" var body: some View { return VStack(spacing: 20) { ScrollView(showsIndicators: false) { Button { run1() } label: { Text("Run") .foregroundColor(.blue) } TextEditor(text: $inputContent) .foregroundColor(.black) .background(Color.gray.opacity(0.2)) } } .foregroundColor(.black) .background(Color.white) } public func run1() { inputContent = "run test 1" print("run1 inputContent is \(inputContent)") } public func run2() { // inputContent should be "run test 1" but output is "init" print("run2 before inputContent is \(inputContent)") inputContent = "run test 2" // inputContent should be "run test 2" but output is still "init" print("run2 after inputContent is \(inputContent)") } }
原因分析
问题核心在于@State的工作机制和SwiftUI视图实例的管理逻辑:
@State是SwiftUI框架内部维护的状态容器,当状态变化时,SwiftUI会创建新的TestView1结构体实例来刷新界面,你在TestVC中保存的testView1是最初创建的旧实例,和当前界面显示的实例并非同一个。- 直接调用旧实例的
run2()方法,操作的是旧实例的inputContent,自然无法获取界面上修改后的最新值。
解决方案
推荐使用ObservableObject作为共享ViewModel的方式,这是SwiftUI与UIKit交互的标准实践,能保证状态在两者间同步一致。
步骤1:创建共享ViewModel
import SwiftUI class TestViewModel: ObservableObject { @Published var inputContent: String = "init" func run1() { inputContent = "run test 1" print("run1 inputContent is \(inputContent)") } func run2() { print("run2 before inputContent is \(inputContent)") inputContent = "run test 2" print("run2 after inputContent is \(inputContent)") } }
步骤2:修改SwiftUI视图
将@State替换为@ObservedObject,引用共享ViewModel:
struct TestView1: View { @ObservedObject var viewModel: TestViewModel var body: some View { VStack(spacing: 20) { ScrollView(showsIndicators: false) { Button { viewModel.run1() } label: { Text("Run") .foregroundColor(.blue) } TextEditor(text: $viewModel.inputContent) .foregroundColor(.black) .background(Color.gray.opacity(0.2)) } } .foregroundColor(.black) .background(Color.white) } }
步骤3:修改UIViewController
在TestVC中创建ViewModel实例,传递给SwiftUI视图,并持有ViewModel而非View实例:
class TestVC: UIViewController { private let viewModel = TestViewModel() // 持有共享ViewModel override func viewDidLoad() { super.viewDidLoad() self.setupData() self.setupUI() } func setupData() { } func setupUI() { self.navigationController?.navigationBar.isTranslucent = false setupNavBar() let contentView = TestView1(viewModel: viewModel) // 传递ViewModel给SwiftUI视图 let hostVC = UIHostingController(rootView: contentView) guard let hostView = hostVC.view else { return } view.addSubview(hostView) hostView.frame = self.view.frame } func setupNavBar() { let item1 = UIBarButtonItem(customView: self.runBtn) navigationItem.rightBarButtonItems = [item1] } lazy var runBtn: UIButton = { let btn = UIButton() btn.setTitle("Run", for: .normal) btn.setTitleColor(.blue, for: .normal) btn.addTarget(self, action: #selector(runBtnAction), for: .touchUpInside) return btn }() @objc func runBtnAction() { viewModel.run2() // 直接调用ViewModel的方法 } }
其他可选方案
如果不想使用ViewModel,也可以选择以下方式:
- 闭包回调:在SwiftUI视图中定义闭包属性,UIKit调用闭包触发操作,由SwiftUI内部处理状态更新。
- NotificationCenter:UIKit发送通知,SwiftUI监听通知并执行对应方法。
但ViewModel的方式最贴合SwiftUI的设计理念,代码结构更清晰、易维护。
内容的提问来源于stack exchange,提问作者mars
相关产品推荐
相关产品推荐

