SwiftUI 在一个 ViewModel 中获取另一个 ViewModel 的值

SwiftUI get values of another ViewModel in a ViewModel

我有这些过滤器,它们都会更新 FilterViewModel 然后负责过滤数据。这些视图之一,SearchAddressView 期望 PlacemarkViewModel 而不是 FilterViewModel,因为它在用户开始输入时提供地址下拉列表。那里有很多代码,所以我不想将这段代码复制到我的 FilterViewModel

但是,我需要将 @Published var placemark: PlacemarkPlacemarkViewModel 读到 FilterViewModel。我正在尝试将 PlacemarkViewModel 导入 FilterViewModel,然后使用 didSet { } 读取它的值,但它不起作用。

所以想法是 .. 当用户搜索地址时,这会更新 PlacemarkViewModel,但 FilterViewModel 也需要获取此值。关于如何实现这一目标的任何想法?

struct FiltersView: View {
    @ObservedObject var filterViewModel: FilterViewModel

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                FilterButtonView(title: LocalizedStringKey(stringLiteral: "category"), systemName: "square.grid.2x2.fill") {
                    CategoryFilterView(filterViewModel: self.filterViewModel)
                }

                FilterButtonView(title: LocalizedStringKey(stringLiteral: "location"), systemName: "location.fill") {
                    SearchAddressView(placemarkViewModel: self.filterViewModel.placemarkViewModel)
                }

                FilterButtonView(title: LocalizedStringKey(stringLiteral: "sort"), systemName: "arrow.up.arrow.down") {
                    SortFilterView(filterViewModel: self.filterViewModel)
                }
            }
        }
    }
}

FilterViewModel

class FilterViewModel: ObservableObject, LoadProtocol {
    @Published var placemarkViewModel: PlacemarkViewModel() {
           didSet {
            print("ok") // nothing
           }
       }
}

PlacemarkViewModel

class PlacemarkViewModel: ObservableObject {
    let localSearchCompleterService = LocalSearchCompleterService()
    let locationManagerService = LocationManagerService()
    @Published var addresses: [String] = []

    // I need this value in my FilterViewMode;
    @Published var placemark: Placemark? = nil
    @Published var query = "" {
        didSet {
            localSearchCompleterService.autocomplete(queryFragment: query) { (addresses: [String]) in
                self.addresses = addresses
            }
        }
    }

    init(placemark: Placemark? = nil) {
        self.placemark = placemark
    }

    var address: String {
        if let placemark = placemark {
            return "\(placemark.postalCode) \(placemark.locality), \(placemark.country)"
        }

        return ""
    }

    func setPlacemark(address: String) {
        locationManagerService.getLocationFromAddress(addressString: address) { (coordinate, error) in
            let location: CLLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
            self.locationManagerService.getAddressFromLocation(location: location) { (placemark: CLPlacemark?) in
                if let placemark = placemark {
                    self.placemark = Placemark(placemark: placemark)
                    self.query = placemark.name ?? ""
                }
            }
        }
    }

    func getAddressFromLocation() {
        locationManagerService.getLocation { (location: CLLocation) in
            self.locationManagerService.getAddressFromLocation(location: location) { (placemark: CLPlacemark?) in
                if let placemark = placemark {
                    self.placemark = Placemark(placemark: placemark)
                    self.query = placemark.name ?? ""
                }
            }
        }
    }
}

这是可能的方法

class FilterViewModel: ObservableObject, LoadProtocol {
    @Published var placemarkViewModel = PlacemarkViewModel()

    private var cancellable: AnyCancellable? = nil
    init() {
        cancellable = placemarkViewModel.$placemark
            .sink { placemark in
                // handle updated placemark here
            }
    }
}

接受的答案有很多代码味道,我觉得有必要在这种污染蔓延到 SwiftUI 开发之前对其进行澄清。

  • 不应以任何方式鼓励嵌套视图模型。

视图模型本身不明确(为什么其中允许控制逻辑/副作用?)

嵌套视图模型?那有什么意思?再一次,没有什么能阻止你 在其中隐藏副作用,这更难跟踪和调试。

您还需要考虑维护对象生命周期(初始化、传递嵌套、保留周期、释放)的成本。

例如; init(placemarkViewModel: PlacemarkViewModel) { self.placemarkViewModel = placemarkViewModel }

我看到的关于嵌套视图模型的论点是 "it's a common practice"。

不,这是一个常见的错误。当有人写这篇文章时,你感觉如何?

`vm1.vm2.vm3.modelY.property1.vm.p2`

因为当你鼓励这样做时,这正是会发生的事情。

  • 网络调用在 init() 中有副作用

MVVM 通常将 ViewModel 视为无害的值类型,而实际上它是一个充满控制/业务逻辑/副作用的引用类型。

这就是一个例子。当您创建 "model" 时,您会发起一个具有副作用的网络请求。这会伤害使用您的 "model".

的不知情的开发人员
  • 解耦网络并使用值类型

网络不应该是您制作参考类型模型的唯一原因。您可以拥有专用的网络服务对象和值类型模型。

如果您从 "ViewModel" 中剥离所有网络,并且您发现剩余的 "ViewModel" 微不足道或愚蠢,那么您就走在了正确的轨道上。

您应该使用 @EnvironmentObject.

而不是拥有两个视图模型并具有隐式依赖关系

例如;

final class SharedState: ObservableObject {
    @Published var placemark: Placemark?
    // other stuff you want to publish

    func updatePlacemark() {
        // network call to update placemark and trigger view update
    }

}
let state = SharedState()
state.updatePlacemark() // explicit call for networking with side effects
// set as environmentObject, e.g.; ContentView().environmentObject(state)

您的 SearchAddressView 可以移除外部参数并直接访问 environmentObject。

因此您可以删除所有经过的视图模型:

FilterButtonView(title: LocalizedStringKey(stringLiteral: "location"), systemName: "location.fill") {
                SearchAddressView()
}

等等,但这让我花哨的视图模型变得毫无用处?

这可能是所有这一切中最大的收获。你不需要它们。它们引入了一层额外的复杂性,弊大于利(例如,你被卡住了)。