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

6

我的数据模型属性是在表视图控制器中声明的,而 SwiftUI 视图是以模态的形式呈现的。我希望呈现的 Form 输入可以操作数据模型。我找到的有关数据流的资源仅适用于 SwiftUI 视图之间,而我在 UIKit 集成方面找到的资源是关于将 UIKit 嵌入 SwiftUI 而不是反过来。

此外,对于值类型(在我的情况下是结构体)的数据模型,是否有一个好的方法?还是将其重新建模为类,使其成为引用类型比较合适?


3
将其更改为ObservableObject类,并在此处和其他位置通过引用传递。 - Asperi
@Asperi 因为SwiftUI控件采用双向绑定,因此更新该对象会如何工作? - Curiosity
使用ObservableObject时,仍然只是将模型的引用传递到SwiftUI视图中。您仍然需要对更改进行差异化处理,并相应地更新表格视图单元,可能使用KVO或Combine或适合您架构的任何内容。至于绑定,您可以在模型本身上创建其他属性,这些属性的类型为Binding<...>,并使用其初始化程序,该初始化程序允许您传递自定义的获取和设置函数,您可以具有消耗实际属性的绑定。 - iltercengiz
你能否提供你的代码和需要传递的参数的说明,以便进行集成? - Asperi
你可能想看一下这个 https://dev59.com/gLbna4cB1Zd3GeqPkPmt - Peter Pohlmann
使用podspec或xcframework将数据拆分为独立模块,并以任何方式注入。 - a.masri
2个回答

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)
    }
}

我希望表单输入能够操作数据模型。
这里提供一个简单的演示,将值类型数据传递到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)
            }
        }
    }
}

6

在组织UI代码时,最佳实践要求分为三个部分:

  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操作转发给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视图。

改进和其他提示:

  1. 如果这对你来说太老派了,你可以使用Combine而不是委托来订阅/发布didSubmit事件。
  2. 在这个简单的示例中,模型只是一个字符串。请随意使用您自己的自定义模型数据类型。
  3. 不能保证MyFormViewModel对象在视图销毁时仍然存在,因此如果您想要它生存更长时间,可能明智的做法是在某个地方保留一个强引用。
  4. $viewModel.name语法是一个魔法,它创建了一个Binding<String>实例,该实例引用了MyFormViewModel的可变name属性。

这也是我的做法,效果很好。合并也很不错,但这意味着我需要进行大量的重构。有什么显著的优势吗?我的意思是,使用协议和委托,我真的可以做任何事情... - Peter Pohlmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接