你如何在 UIKit 视图控制器和它呈现的 SwiftUI 视图之间共享数据模型?

Aus*_*lon 6 uikit swift swiftui combine

我的数据模型属性在我的表视图控制器中声明,并且 SwiftUI 视图以模态呈现。我想要提供的Form输入来操作数据模型。我在数据流中找到的资源只是在 SwiftUI 视图之间,而我在 UIKit 集成中找到的资源是在 SwiftUI 中嵌入 UIKit 而不是相反。

此外,对于值类型(在我的情况下是结构)数据模型是否有一种很好的方法,或者是否值得将其重新建模为一个类以便它是一个引用类型?

Asp*_*eri 9

我们来分析一下...

我的数据模型属性在我的表视图控制器中声明,并且 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

我想要提供的表单输入来操作数据模型。

这里是上面的更新,其中包含将值类型数据传递到 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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


bat*_*str 6

在组织 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)")
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,很容易将 UI 操作转发到 UIKit 控制器。一种标准方法是使用委托协议:

protocol MyFormViewModelDelegate: class {
    func didSubmit(viewModel: MyFormViewModel)
}

class MyFormViewModel {
    weak var delegate: MyFormViewModelDelegate?
    func submit() {
        self.delegate?.didSubmit(viewModel: self)
    }
    ...
Run Code Online (Sandbox Code Playgroud)

最后,你的 UIViewController 可以实现 MyFormViewModelDelegate,创建一个 MyFormViewModel 实例,并通过设置self为delegate),然后将 MyFormViewModel 对象传递给 MyForm 视图。

改进和其他提示:

  1. 如果这对您来说太老套了,您可以使用 Combine 而不是委托来订阅/发布didSubmit事件。
  2. 在这个简单的例子中,模型只是一个字符串。随意使用您的自定义模型数据类型。
  3. 无法保证 MyFormViewModel 对象在视图被销毁时保持活动状态,因此如果您希望它存活更长时间,在某处保留强引用可能是明智的。
  4. $viewModel.name语法是一种创建Binding<String>引用nameMyFormViewModel的可变属性的实例的魔法。