As said in the title, 2 of the 3 @AppStorage variables aren't updated the first time. So, when the view is dismissed, I have to re-open it and then at the second dismiss they're all updated. To be precise, only storedCityName is updated the first time.
Here's the view:
import SwiftUI
struct SearchView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var searchManager: SearchManager
@AppStorage("storedUserLatitude") private var storedUserLatitude: Double?
@AppStorage("storedUserLongitude") private var storedUserLongitude: Double?
@AppStorage("storedCityName") private var storedCityName: String?
var body: some View {
VStack {
TextField("", text: $searchManager.search)
ForEach(searchManager.searchResults, id: \.self) { result in
HStack {
VStack {
Text(result.title)
Text(result.subtitle)
}
}
.onTapGesture { update(result.title) }
}
}
}
private func update(_ city: String) {
searchManager.searchLocation(city)
storedUserLatitude = searchManager.userLocation?.coordinate.latitude
storedUserLongitude = searchManager.userLocation?.coordinate.longitude
storedCityName = city
dismiss()
}
}
And, here's the manager:
import Foundation
import MapKit
import Combine
class SearchManager: NSObject, ObservableObject, MKLocalSearchCompleterDelegate {
private let completer = MKLocalSearchCompleter()
@Published var search = ""
@Published var searchResults = [MKLocalSearchCompletion]()
@Published var userLocation: CLLocation?
private var publisher: AnyCancellable?
override init() {
super.init()
completer.delegate = self
completer.resultTypes = .address
publisher = $search
.receive(on: RunLoop.main)
.sink(receiveValue: { string in
self.completer.queryFragment = string
})
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
searchResults = completer.results
}
func searchLocation(_ city: String) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(city) { placemarks, error in
guard let placemark = placemarks?.first else { return }
self.userLocation = placemark.location
}
}
}
I really don't understand why it works at the second try but not at the first one.
I also tried to delay the dismiss with DispatchQueue.main.asyncAfter(deadline: .now() + 2) { dismiss() } to see if the @AppStorage variables needed time to update but it doesn't change anything.
Do you have any idea of what's going on?
Thanks, in advance!
The real problem here is that you call your reverse geocoder with
searchManager.searchLocation(city), which uses an async function to do the reverse geocoding. While you pass a block to that to set the values in your search manager, the code inSearchViewisn't waiting for that information.On the second try, your SearchManager environment object has had time to update with the geocoding from the first attempt.
You should consider updating
searchLocationto make sure that it only runs the subsequent code inupdateonce the geocoding has happened. One way would be to allow it to receive a block:There are neater ways, and you'd need to handle the error path properly, but hopefully this gives you the right idea.