ObservedObject 不会注意到 ObservableObject Array 的变化
Posted
技术标签:
【中文标题】ObservedObject 不会注意到 ObservableObject Array 的变化【英文标题】:ObservedObject does not notice changes of ObservableObject Array 【发布时间】:2021-08-02 18:22:10 【问题描述】:我在将 ObservedObject 与 SwiftUI 一起使用时遇到问题。我的应用有两个主要视图:
-
第一个视图称为 MapView,其中包含一个带有不同注释的地图。
第二个视图称为 MapSettingsView,有一个 DatePicker 和一个用于类别的 Picker,我可以在其中决定 MapView 中哪些注释可见,哪些不可见。
每个注释都有特定的属性,例如日期、类别和坐标(纬度、经度)。 每个注释都保存在我的 Firebase 实时数据库中,因此为了获取它们,我构建了一个 API,它可以调用我想要的所有信息,效果很好。
在 MapSettingsView 中选择了我想要的所有属性后,我有一个按钮,它在我的 FirebaseTasks 类中调用一个函数 (loadPublicLocations)。然后,此函数根据 Firebase 中的这些属性调用所有位置。这也很有效,因为我打印了所有想要的位置。为了根据新的位置数组刷新 MapView,FirebaseTasks 中的所有位置都附加到 @Published var publicLocations = [LocationModel]()
。在 MapView 内部,注释基于这个数组,我通过 @ObservedObject
属性获得。
现在的问题是:在 FirebaseTasks 中,所有想要的位置都附加到“publicLocations”数组中。 MapView 不会刷新,虽然注解是基于 ObservedObject taskModel.publicLocations
。
地图视图:
struct MapView: View
@ObservedObject var taskModel = FirebaseTasks()
@State var showSettings = false
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: 25.7617,
longitude: 80.1918
),
span: MKCoordinateSpan(
latitudeDelta: 10,
longitudeDelta: 10
)
)
var body: some View
VStack
HStack
Button(action:
showSettings.toggle()
)
Image(systemName: "line.horizontal.3.decrease")
.font(.title)
.foregroundColor(.black)
.sheet(isPresented: $showSettings)
MapSettings(mapViewModel: MapSettingsViewModel("all", category: "all", lat: 21, long: 21))
.padding(.horizontal)
.padding(.top)
.padding(.bottom,10)
Map(coordinateRegion: $region, annotationItems: taskModel.publicLocations, annotationContent: (location) -> MapMarker in
MapMarker(coordinate: CLLocationCoordinate2D(latitude: location.lat!, longitude: location.long!), tint: .red) // does not get data on refreshed MapSettings
)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
Firebase 任务:
class FirebaseTasks: ObservableObject
// Model
@Published var publicLocations = [LocationModel]()
didSet
print(publicLocations) // works
// Location related
func loadPublicLocations(category: String, date: String, currentLat: Double, currentLong: Double)
publicLocations.removeAll()
PublicLocationApi.system.addPublicRadiusLocationObserver(category, date: date, currentLat: currentLat, currentLong: currentLong) (location) in
print(location) // works
self.publicLocations.append(location)
【问题讨论】:
在我稍微简化了这段代码以便我可以运行它之后,它非常适合我。问题可能在于您未显示的代码,例如LocationModel
。
【参考方案1】:
由于您没有提供MapSettingsView
,所以有些可能性我无法给出明确的原因
首先,很可能取决于您如何称呼您的 .sheet
内容。
您正在创建 FirebaseTasks()
的单独实例,每次调用 FirebaseTasks()
时,它都是不同的实例,一个不知道另一个在做什么。
其次,您在没有@StateObject
的View
中创建ObservableObject
s。下面的两行都是不安全的,当View
重新加载时,可能会导致您的所有模型数据丢失。
@ObservedObject var taskModel = FirebaseTasks()
MapSettings(mapViewModel: MapSettingsViewModel("all", category: "all", lat: 21, long: 21))
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
import SwiftUI
import MapKit
struct ChainedMapView: View
//@ObservedObject is used when the Observable Object is coming from the previous View or a class
@StateObject var taskModel = FirebaseTasks()
@State var showSettings = false
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: 25.7617,
longitude: 80.1918
),
span: MKCoordinateSpan(
latitudeDelta: 10,
longitudeDelta: 10
)
)
var body: some View
VStack
HStack
Button(action:
showSettings.toggle()
)
Image(systemName: "line.horizontal.3.decrease")
.font(.title)
.foregroundColor(.black)
//Creating Observable Objects in a View without StateObject is considered unsafe
//https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
.sheet(isPresented: $showSettings)
//Your load should be related to the region so it is visible
MapSettingsView(category: "all", lat: region.center.latitude, long: region.center.longitude)
//You have to pass the same instance of the ObservableObject
.environmentObject(taskModel)
.padding(.horizontal)
.padding(.top)
.padding(.bottom,10)
Map(coordinateRegion: $region, annotationItems: taskModel.publicLocations, annotationContent: (location) -> MapMarker in
MapMarker(coordinate: location.coordinates, tint: .red)
)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
class FirebaseTasks: ObservableObject
// Model
@Published var publicLocations = [LocationModel]()
didSet
print(publicLocations) // works
// Location related
func loadPublicLocations(category: String, date: String, currentLat: Double, currentLong: Double)
publicLocations.removeAll()
//PublicLocationApi.system.addPublicRadiusLocationObserver(category, date: date, currentLat: currentLat, currentLong: currentLong) (location) in
//Created random objects since I dont have access to your API
for _ in 0...10
let location = LocationModel(coordinates: CLLocationCoordinate2D(latitude: Double.random(in: currentLat...currentLat+1), longitude: Double.random(in: currentLong...currentLong+1)), date: Date())
print(location) // works
self.publicLocations.append(location)
struct LocationModel: Identifiable
let id: UUID = UUID()
var coordinates: CLLocationCoordinate2D
var date: Date
struct MapSettingsView: View
//If you are doing something like this you are creating a seperate instance
//One does not know what the other is doing
//@StateObject var taskModel = FirebaseTasks()
//This is the shared Object
@EnvironmentObject var taskModel: FirebaseTasks
//You have not described how you use this
//@StateObject var vm: MapSettingsViewModel = MapSettingsViewModel()
let category: String
let lat: Double
let long: Double
var body: some View
Button("load", action:
taskModel.loadPublicLocations(category: category, date: Date().description, currentLat: lat, currentLong: long)
).onAppear(perform:
//To put initial values into your ViewModel it is best to do it here.
//Dont Create the ObservableObject in the body of a View
)
struct ChainedMapView_Previews: PreviewProvider
static var previews: some View
ChainedMapView()
【讨论】:
非常感谢,这是我的错。我没有为 MapSettings 中的 taskModel 和 MapView 中的属性@StateObject
使用共享属性 @EnvironmentObject
。这解决了我的问题。【参考方案2】:
在 SwiftUI 中触发 UI 更新的任何代码都必须在 main thread 上运行。如果 ObservableObject
内的代码更改了另一个线程上的 @Published
属性,则更改可能不会反映在 UI 中(即使它会正确触发 didSet
内的代码,这就是它起作用的原因)。
您用于定位服务的 API(例如 PublicLocationApi
)在后台线程而不是主线程上调用完成处理程序是很常见的。我的猜测是这里正在发生的事情。要在主线程上更新 UI,请替换
self.publicLocations.append(location)
与
DispatchQueue.main.async
self.publicLocations.append(location)
DispatchQueue.main.async
函数在主线程上运行——传递给它的闭包中的代码,因此它对于进行 UI 更新很有用。
【讨论】:
这是一个好点 - 但肯定会弹出诸如“不允许从后台线程发布更改”之类的警告吗?地图仍在更新(至少对我而言)。以上是关于ObservedObject 不会注意到 ObservableObject Array 的变化的主要内容,如果未能解决你的问题,请参考以下文章
当从另一个 ObservedObject 链接到 Published 属性时,视图不会对更改做出反应
SwiftUI - ObservedObject 永远不会被释放
SwiftUI - 尽管使用了 @Published 和 @ObservedObject,但视图不会更新
SwiftUI:如果 @ObservedObject 是 UIViewController 的子类,则不会重新加载视图内容。这是一个错误还是我错过了啥?
SwiftUI - 从 Swift 类启动的可观察对象不会更新 ContentView() 上的 @ObservedObject