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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 18:32:27