您如何在 UIKit 视图控制器和它呈现的 SwiftUI 视图之间共享数据模型?
How do you share a data model between a UIKit view controller and a SwiftUI view that it presents?
我的数据模型 属性 在我的 table 视图控制器中声明,SwiftUI 视图以模态方式呈现。我想要呈现的 Form
输入来操作数据模型。我在数据流上找到的资源只是在 SwiftUI 视图之间,而我在 UIKit 集成上找到的资源是在 SwiftUI 中嵌入 UIKit,而不是相反。
此外,值类型(在我的例子中是结构)数据模型是否有好的方法,或者是否值得将其重塑为 class 以便它成为引用类型?
在组织 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动作转发给UI套件控制器。一种标准方法是使用委托协议:
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 视图。
改进和其他提示:
- 如果这对您来说太old-school,您可以使用 Combine 而不是委托给 subscribe/publish 一个
didSubmit
事件。
- 在这个简单的示例中,模型只是一个字符串。随意使用您的自定义模型数据类型。
- 无法保证 MyFormViewModel 对象在视图被销毁时保持活动状态,因此如果您希望它存活更长时间,在某处保留强引用可能是明智的。
$viewModel.name
语法是一种魔法,它创建了一个引用 MyFormViewModel 的可变 name
属性 的 Binding<String>
实例。
我们分析一下...
My data model property is declared in my table view controller and the SwiftUI view is modally presented.
这就是你现在拥有的(可能已简化)
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)
}
}
I'd like the presented Form input to manipulate the data model.
这里是上面的更新,其中包含将值类型数据传递到 SwiftUI 视图并将其取回的简单演示 updated/modified/processed,无需对 UIKit 部分进行任何必要的重构。
这个想法很简单 - 你将当前模型按值传递给 SwiftUI,然后 return 它在完成回调中返回并更新并应用于本地 属性 (因此,如果设置了任何观察者,它们都可以作为预期)
测试 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)
}
}
}
}
我的数据模型 属性 在我的 table 视图控制器中声明,SwiftUI 视图以模态方式呈现。我想要呈现的 Form
输入来操作数据模型。我在数据流上找到的资源只是在 SwiftUI 视图之间,而我在 UIKit 集成上找到的资源是在 SwiftUI 中嵌入 UIKit,而不是相反。
此外,值类型(在我的例子中是结构)数据模型是否有好的方法,或者是否值得将其重塑为 class 以便它成为引用类型?
在组织 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动作转发给UI套件控制器。一种标准方法是使用委托协议:
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 视图。
改进和其他提示:
- 如果这对您来说太old-school,您可以使用 Combine 而不是委托给 subscribe/publish 一个
didSubmit
事件。 - 在这个简单的示例中,模型只是一个字符串。随意使用您的自定义模型数据类型。
- 无法保证 MyFormViewModel 对象在视图被销毁时保持活动状态,因此如果您希望它存活更长时间,在某处保留强引用可能是明智的。
$viewModel.name
语法是一种魔法,它创建了一个引用 MyFormViewModel 的可变name
属性 的Binding<String>
实例。
我们分析一下...
My data model property is declared in my table view controller and the SwiftUI view is modally presented.
这就是你现在拥有的(可能已简化)
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)
}
}
I'd like the presented Form input to manipulate the data model.
这里是上面的更新,其中包含将值类型数据传递到 SwiftUI 视图并将其取回的简单演示 updated/modified/processed,无需对 UIKit 部分进行任何必要的重构。
这个想法很简单 - 你将当前模型按值传递给 SwiftUI,然后 return 它在完成回调中返回并更新并应用于本地 属性 (因此,如果设置了任何观察者,它们都可以作为预期)
测试 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)
}
}
}
}