SwiftUI 在一个 ViewModel 中获取另一个 ViewModel 的值
SwiftUI get values of another ViewModel in a ViewModel
我有这些过滤器,它们都会更新 FilterViewModel
然后负责过滤数据。这些视图之一,SearchAddressView
期望 PlacemarkViewModel
而不是 FilterViewModel
,因为它在用户开始输入时提供地址下拉列表。那里有很多代码,所以我不想将这段代码复制到我的 FilterViewModel
但是,我需要将 @Published var placemark: Placemark
从 PlacemarkViewModel
读到 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()
}
等等,但这让我花哨的视图模型变得毫无用处?
这可能是所有这一切中最大的收获。你不需要它们。它们引入了一层额外的复杂性,弊大于利(例如,你被卡住了)。
我有这些过滤器,它们都会更新 FilterViewModel
然后负责过滤数据。这些视图之一,SearchAddressView
期望 PlacemarkViewModel
而不是 FilterViewModel
,因为它在用户开始输入时提供地址下拉列表。那里有很多代码,所以我不想将这段代码复制到我的 FilterViewModel
但是,我需要将 @Published var placemark: Placemark
从 PlacemarkViewModel
读到 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()
}
等等,但这让我花哨的视图模型变得毫无用处?
这可能是所有这一切中最大的收获。你不需要它们。它们引入了一层额外的复杂性,弊大于利(例如,你被卡住了)。