【发布时间】:2021-08-18 16:01:00
【问题描述】:
我正在尝试将发布的值从一个视图模型传递到另一个视图模型(即子视图模型需要访问源并能够操作值)。我敢打赌这很简单,但是,我似乎无法在 MVVM 模式中找到“正确”的做法。
我尝试过使用@Bindings、Binding<Value>。我设法让它与@EnvironmentObject一起工作,但这意味着视图必须处理它,因为我无法将它传递给逻辑应该在的视图模型(最终我想在子视图模型中操作数据流使用组合)。我错过了什么?
我用以下操场代码简化了这种情况:
import SwiftUI
import PlaygroundSupport
class InitialViewModel: ObservableObject {
@Published var selectedPerson: Person = Person(firstName: "", surname: "")
}
struct InitialView: View {
@StateObject var viewModel = InitialViewModel()
var body: some View {
ButtonView(selectedPerson: Published(wrappedValue: viewModel.selectedPerson) )
SelectedPersonView(selectedPerson: Published(wrappedValue: viewModel.selectedPerson))
}
}
class ButtonViewModel: ObservableObject {
@Published var selectedPerson: Person
init(selectedPerson: Published<Person>) {
self._selectedPerson = selectedPerson
}
func toggleSelectedPerson() {
if selectedPerson.firstName.isEmpty {
selectedPerson = Person(firstName: "Boris", surname: "Johnson")
} else {
selectedPerson = Person(firstName: "", surname: "")
}
}
}
struct ButtonView: View {
@ObservedObject var viewModel: ButtonViewModel
init(selectedPerson: Published<Person>) {
self._viewModel = ObservedObject(wrappedValue: ButtonViewModel(selectedPerson: selectedPerson))
}
var body: some View {
Button(action: { viewModel.toggleSelectedPerson()} ) {
Text("Press to select person")
}
}
}
class SelectedPersonViewModel: ObservableObject {
@Published var selectedPerson: Person
init(selectedPerson: Published<Person>) {
self._selectedPerson = selectedPerson
}
}
struct SelectedPersonView: View {
@ObservedObject var viewModel: SelectedPersonViewModel
init(selectedPerson: Published<Person>) {
self._viewModel = ObservedObject(wrappedValue: SelectedPersonViewModel(selectedPerson: selectedPerson))
}
var body: some View {
if viewModel.selectedPerson.firstName.isEmpty {
Text("No person selected yet")
} else {
Text("Person \(viewModel.selectedPerson.firstName) selected!")
}
}
}
struct Person {
let firstName: String
let surname: String
}
let view = InitialView()
PlaygroundPage.current.setLiveView(view)
本质上,当我按下按钮时,selectedPerson 属性应该会更新,视图也会相应更新。
8 月 19 日编辑
好的,为了澄清这个问题,我添加了我正在处理的实际代码的一个非常简化的版本。希望这能解释我为什么要研究这个问题。
注意:我知道编译错误。这只是为了展示我在寻找什么。
struct ItemOption: Identifiable {
let id: Int
let name: String
var dependentOn: [Int]? // dependencies where, choosing one option opens up more options
let values: [ItemOptionValue]
}
struct ItemOptionValue: Identifiable {
let id: Int
let name: String?
}
class OptionViewModel: ObservableObject { // all options e.g. config options on a car
@Published var selectedOptions = [Int:[Int]]() // Structure of [OptionID: [ValueID]
@Published var allOptions = [ItemOption]()
@Published var filteredOptions = [ItemOption]()
init(options: [ItemOption]) {
self.allOptions = options
filterAvailableOptions()
}
func filterAvailableOptions() {
// Combine code to filter viewable options depending on dependencies that may appear in selectedOption
}
}
struct OptionView: View {
@StateObject var viewModel: OptionViewModel
init(options: [ItemOption]) {
self.viewModel = OptionViewModel(options: options)
}
var body: some View {
ForEach(viewModel.filteredOptions) { section in
OptionTypeView(selectedOptions: viewModel.selectedOptions, optionValues: section.values)
}
}
}
class OptionTypeViewModel: ObservableObject { // each option type e.g. colour on car, wheel trims etc
}
struct OptionTypeView: View {
var selectedOptions: [Int:[Int]]
var optionValues: [ItemOptionValue]
@StateObject var viewModel = OptionTypeViewModel()
var body: some View {
ForEach(optionValues) { value in
OptionValueView(selectedOptions: selectedOptions)
}
}
}
class OptionValueViewModel: ObservableObject { // values of each option e.g. each colour choice
@Published var isOptionSelected: Bool
@Published var selectedOptions: [Int:[Int]] // passed on value (Binding?) from OptionViewModel
init(selectedOptions: [Int:[Int]]) {
self.selectedOptions = selectedOptions
}
func trackSelectedOptions(optionID: Int, valueID: Int) {
$selectedOptions
// ... Combine mapping to check if value exists in selectedOptions that originally comes from OptionViewModel
.assign(\.isOptionSelected, on: self)
}
func removeOption() {
// code to remove value id from selectedOptions
}
func addOption() {
// code to add value id to selectedOptions
}
}
struct OptionValueView: View {
@StateObject var viewModel: OptionValueViewModel
init(selectedOptions: [Int: [Int]]) {
self.viewModel = OptionValueViewModel(selectedOptions: selectedOptions)
}
var body: some View {
if viewModel.isOptionSelected {
Text("Option is selected")
Button(action: { viewModel.removeOption } ) {
Text("Remove option")
}
} else {
Text("Option is NOT selected")
Button(action: { viewModel.addOption } ) {
Text("Add option")
}
}
}
}
一种选择可能是将它们全部连接到一个大型视图(带有视图模型)和处理子视图的 func/computed 变量中,以便访问原始值,但问题仍然是 OptionValueView 仍然需要计算它自己的值以驱动视图上显示的内容,这需要在它自己的视图模型中完成。
【问题讨论】:
-
这是否与您的实际项目一样复杂?
-
完全没有,但基本概念保持不变。在实际项目中,子视图需要访问父视图值(在本例中为字典),以便订阅并将日期流操作为子视图所需的子视图模型中的简化动态值 (Int)。
-
我问是因为你永远不会做三个不同的视图模型只是共享一个人。这一切都将在您与
environmentObject共享的视图模型中完成。如果子视图只需要访问某个值,则只需将该值传递为Binding(如果需要)或将整个视图模型传递为environmentObject,不要创建新的。 -
我明白你在做什么。它不会像您期望的那样工作,因为您不会像您期望的那样将一个与另一个链接起来。您需要一个集中的“存储”位置来存放您的人员以及多个视图模型可以访问的替代方案。
-
当然,这取决于您的最终真相来源在哪里。数据库?一份文件?一个网站?视图模型将访问您的管理器/存储以获取数据。它不必是单例。最终,真相应该只有一个来源,那就是您的存储空间。