你如何在 UIKit 视图控制器和它呈现的 SwiftUI 视图之间共享数据模型?
Posted
技术标签:
【中文标题】你如何在 UIKit 视图控制器和它呈现的 SwiftUI 视图之间共享数据模型?【英文标题】:How do you share a data model between a UIKit view controller and a SwiftUI view that it presents? 【发布时间】:2020-09-08 11:47:35 【问题描述】:我的数据模型属性在我的表视图控制器中声明,SwiftUI 视图以模态方式呈现。我想要提供的Form
输入来操作数据模型。我在数据流上找到的资源只是在 SwiftUI 视图之间,而我在 UIKit 集成上找到的资源是关于将 UIKit 嵌入到 SwiftUI 中,而不是相反。
此外,对于值类型(在我的例子中是结构)数据模型是否有一种好的方法,或者是否值得将其重新建模为一个类以便它成为一个引用类型?
【问题讨论】:
使它成为 ObservableObject 类并在这里和那里通过引用传递。 @Asperi 既然 SwiftUI 控件采用双向绑定,那么更新该对象将如何工作? 使用 ObservableObject,它仍然只是对传递给 SwiftUI 视图的模型的引用。您仍然需要区分更改并相应地更新表格视图单元格,可能使用 KVO 或 Combine 或任何适合您的架构的东西。至于绑定,您可以在模型本身上创建Binding<...>
类型的其他属性,并使用其允许您传递自定义 get 和 set 函数的初始化程序,您可以拥有一个使用实际属性的绑定。
您能否提供您的代码以与您想在哪里传递的解释集成在一起?
你可能想看看这个***.com/questions/58442827/…
【参考方案1】:
我们来分析一下……
我的数据模型属性在我的表视图控制器中声明,SwiftUI 视图以模态方式呈现。
这就是你现在所拥有的(可能是简化的)
struct DataModel
var value: String
class ViewController: UIViewController
var dataModel: DataModel
// ... some other code
func showForm()
let formView = FormView()
let controller = UIHostingController(rootView: formView)
self.present(controller, animating: true)
我想要显示的表单输入来操作数据模型。
这里是上面的更新,其中包含将值类型数据传递到 SwiftUI 视图的简单演示,并在无需对 UIKit 部分进行任何重构的情况下将其更新/修改/处理。
这个想法很简单——你通过值将当前模型传递给 SwiftUI,并在完成回调中将其返回并更新并应用于本地属性(因此,如果设置了任何观察者,它们都会按预期工作)
使用 Xcode 12 / ios 14 测试。
class ViewController: UIViewController
var dataModel: DataModel
// ... some other code
func showForm()
let formView = FormView(data: self.dataModel) [weak self] newData in
self?.dismiss(animated: true)
self?.dataModel = newData
let controller = UIHostingController(rootView: formView)
self.present(controller, animated: true)
struct FormView: View
@State private var data: DataModel
private var completion: (DataModel) -> Void
init(data: DataModel, completion: @escaping (DataModel) -> Void)
self._data = State(initialValue: data)
self.completion = completion
var body: some View
Form
TextField("", text: $data.value)
Button("Done")
completion(data)
【讨论】:
【参考方案2】:在组织 UI 代码时,最佳实践要求包含 3 个部分:
-
视图(仅视觉结构、样式、动画)
模型(您的数据和业务逻辑)
连接视图和模型的秘诀
在 UIKit 中,我们使用MVP 方法,其中 UIViewController 子类通常代表秘密酱汁部分。
在 SwiftUI 中,由于提供了数据绑定工具,使用 MVVM 方法更容易。在 MVVM 中,“ViewModel”是秘诀。它是一个自定义结构,用于保存模型数据以供您的视图呈现,在模型数据更新时触发视图更新,并转发 UI 操作以对您的模型执行某些操作。
例如,编辑名称的表单可能如下所示:
struct MyForm: View
let viewModel: MyFormViewModel
var body: some View
Form
TextField("Name", text: $viewModel.name)
Button("Submit", action: self.viewModel.submit() )
class MyFormViewModel
var name: String // implement a custom setter if needed
init(name: String) this.name = name
func submit()
print("submitting: \(name)")
有了这个,很容易将 UI 动作转发到 UIKit 控制器。一种标准方法是使用委托协议:
protocol MyFormViewModelDelegate: class
func didSubmit(viewModel: MyFormViewModel)
class MyFormViewModel
weak var delegate: MyFormViewModelDelegate?
func submit()
self.delegate?.didSubmit(viewModel: self)
...
最后,你的 UIViewController 可以实现 MyFormViewModelDelegate,创建一个 MyFormViewModel 实例,并通过将 self
设置为 delegate
来订阅它,然后将 MyFormViewModel 对象传递给 MyForm 视图。
改进和其他提示:
-
如果这对您来说太老套了,您可以使用 Combine 而不是委托来订阅/发布
didSubmit
事件。
在这个简单的示例中,模型只是一个字符串。随意使用您的自定义模型数据类型。
无法保证 MyFormViewModel 对象在视图被销毁时仍保持活动状态,因此如果您希望在某处保持强引用更长时间,则可能是明智之举。
$viewModel.name
语法是一种魔术,它创建一个引用 MyFormViewModel 的可变 name
属性的 Binding<String>
实例。
【讨论】:
我也是这样做的,效果很好。组合也会很棒,但这对我来说意味着很多重构。有什么显着优势吗?我的意思是,对于协议和委托,我真的没有什么不能做的......以上是关于你如何在 UIKit 视图控制器和它呈现的 SwiftUI 视图之间共享数据模型?的主要内容,如果未能解决你的问题,请参考以下文章
如何知道 UIKit 视图控制器中的 swiftUI 事件? ||如何提供 swiftUI 和 uikit 之间的通信
如何从 SwiftUI 视图转到 UIKit TabViewController?