您如何在 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 个部分:

  1. 视图(仅视觉结构、样式、动画)
  2. 模型(您的数据和业务逻辑)
  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实例,通过设置selfdelegate订阅它),然后通过MyFormViewModel 对象到 MyForm 视图。

改进和其他提示:

  1. 如果这对您来说太old-school,您可以使用 Combine 而不是委托给 subscribe/publish 一个 didSubmit 事件。
  2. 在这个简单的示例中,模型只是一个字符串。随意使用您的自定义模型数据类型。
  3. 无法保证 MyFormViewModel 对象在视图被销毁时保持活动状态,因此如果您希望它存活更长时间,在某处保留强引用可能是明智的。
  4. $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)
            }
        }
    }
}

backup