SwiftUI 获取 ViewModel 中另一个 ViewModel 的值

Rex*_*xha 4 xcode mvvm swift swift5 swiftui

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

但是,我需要阅读@Published var placemark: PlacemarkPlacemarkViewModelFilterViewModel。我正在尝试将 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)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

过滤视图模型

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

地标视图模型

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

Jim*_*lai 6

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

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

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

嵌套视图模型?那有什么意思?同样,没有什么可以阻止您在其中隐藏副作用,这更难以跟踪和调试。

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

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

我看到的关于嵌套视图模型的争论是“这是一种常见的做法”。

不,这是一个常见的错误。当有人写下这句话时,你有何感受?

`vm1.vm2.vm3.modelY.property1.vm.p2`
Run Code Online (Sandbox Code Playgroud)

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

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

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

这就是一个这样的例子。当您创建“模型”时,您会发起具有副作用的网络请求。这会伤害使用你的“模型”的不知情的开发人员。

  • 解耦网络和使用价值类型

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

如果您从“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)
Run Code Online (Sandbox Code Playgroud)

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

因此,您可以删除所有传递的视图模型:

FilterButtonView(title: LocalizedStringKey(stringLiteral: "location"), systemName: "location.fill") {
                SearchAddressView()
}
Run Code Online (Sandbox Code Playgroud)

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

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