【发布时间】:2021-12-27 03:22:47
【问题描述】:
我从 Firestore 表中获取一个数组。显然是实时的,因此任何新添加的文档或对现有文档的修改都会反映在视图中。这适用于字符串和数字等简单属性,但我也需要显示和刷新其他数据。
我也在存储坐标,如果坐标发生变化,我需要调用geocoder.reverseGeocodeLocation() 以便将地标移出该位置并将地址显示为字符串。为此,我创建了一个名为 PlacemarkService (code provided below).
问题是这样的。每当修改模型时,数据都来自PackagesViewModel,它有一个@Published var results = [Package]() 这是由Firestore(可编码)动态修改的。然后,对数组造成的任何更改都将导致 PackagesView 内的 List 被刷新,这将导致创建 PackageView(packageService: PackageService(package: package)) 的新实例,并且由于 PackageService 被作为参数传递,这里是一个新实例的PackageService 将被创建。最后,这将触发该服务内部的 init() 被调用。
@Published var results = [Package]() -> List -> PackageView(packageService: PackageService(package: package)) -> init()
所以现在,如果我按照这个流程并在 init 内触发 getPlacemarks(),一切正常(大多数情况下),如果包模型发生更改,此函数将在 init 上调用,并将反转修改位置的 GeocodeLocation。但是这里有一个大问题,这就是原因。我无法将此逻辑放在 init 上,因为我无法对 for 循环内的数百个位置进行地理编码。我在一个列表中显示了多个包,而 MapKit 不允许这样做。
显然,在显示PackageView之后需要触发此逻辑,因此选择其中一个包。
如果我在 PackageView 出现时调用 packageService.getPlacemarks(),这第一次可以正常工作,但是......当 @Published var results = [Package]() 更新时不会触发 onAppear。
所以最后的问题是:
在哪里调用 packageService.getPlacemarks(),以便在 @Published var results = [Package] 中的 Package 更新时调用它
但由于我上面解释的原因,不在 PlacemarkService 的 init 上。
对不起,解释太长了,我只是想清楚一点。
class PackageService: ObservableObject {
var package: Package
private var cancellables = Set<AnyCancellable>()
@Published var sourcePlacemarkService = PlacemarkService()
@Published var destinationPlacemarkService = PlacemarkService()
var cancellable: AnyCancellable? = nil
init(package: Package) {
self.package = package
startListening()
}
func startListening() {
// Listen to placemark changes.
cancellable = Publishers.CombineLatest(sourcePlacemarkService.$placemark, destinationPlacemarkService.$placemark).sink(receiveValue: {_ in
// Publish changes manually to the view.
self.objectWillChange.send()
})
}
// Get placemarks from locations
func getPlacemarks() {
sourcePlacemarkService.reverseGeocodeLocation(location: package.source.toCLLocation)
destinationPlacemarkService.reverseGeocodeLocation(location: package.destination.toCLLocation)
}
}
class PackagesViewModel: ObservableObject {
@Published var results = [Package]()
func load() {
query.addSnapshotListener { (querySnapshot, error) in
// updates results array when a document is modified.
}
}
}
struct PackagesView: View {
@StateObject var packagessViewModel = PackagesViewModel()
var body: some View {
List(packagessViewModel.results, id: \.self) { package in
NavigationLink(destination: PackageView(packageService: PackageService(package: package))) {
Text(package.title)
}
}
}
}
struct PackageView: View {
@ObservedObject var packageService: PackageService
func onAppear() {
packageService.getPlacemarks()
}
var body: some View {
// show the address from placemark after it is geocoded.
VStack {
Text(packageService.sourcePlacemarkService.placemark.title)
Text(packageService.destinationPlacemarkService.placemark.title)
}
.onAppear(perform: onAppear)
}
}
class PlacemarkService: ObservableObject {
@Published var placemark: CLPlacemark?
init(placemark: CLPlacemark? = nil) {
self.placemark = placemark
}
func reverseGeocodeLocation(location: CLLocation?) {
if let location = location {
geocoder.reverseGeocodeLocation(location, completionHandler: { (placemark, error) in
// some code here
self.placemark = placemark
})
}
}
}
struct Package: Identifiable, Codable {
@DocumentID var id: String?
var documentReference: DocumentReference
var uid: String
var title: String
var description: String
var source, destination: GeoPoint
var amount: Double
var createdAt: Timestamp = Timestamp()
var paid: Bool = false
}
【问题讨论】:
-
把它放在
didSet中换成results
标签: ios swift swiftui swift5 combine