Background
I'm trying to build a class that will easily convert string like addresses into a CLLocationCoordinate2D for later use that will be saved to a database.
I have a class that is similar to below:
final class PublishMapData: ObservableObject {
@Published var userAddressLat: Double = 0.0
@Published var userAddressLong: Double = 0.0
func saveMapData(address: String){
let address = "One Apple Park Way, Cupertino, CA 95014" //simulating the call from a save button for instance
convertAddress(address: address)
print(String(userAddressLat)) //this prints 0.0
print(String(userAddressLong)) //this print 0.0
//{...extra code here...}
//this is where I would be storing the coordinates into something like Firestore for later use
}
func convertAddress(address: String) {
getCoordinate(addressString: address) { (location, error) in
if error != nil {
return
}
DispatchQueue.main.async {
self.userAddressLat = location.latitude
print(self.userAddressLat) //this prints the correct value
self.userAddressLong = location.longitude
print(self.userAddressLong) //this prints the correct value
}
}
}
private func getCoordinate(addressString : String, completionHandler: @escaping(CLLocationCoordinate2D, NSError?) -> Void ) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(addressString) { (placemarks, error) in
if error == nil {
if let placemark = placemarks?[0] {
let location = placemark.location!
completionHandler(location.coordinate, nil)
return
}
}
completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?)
}
}
}
For some reason, I'm not getting the lat, long values from the convertAddress function to properly get stored within the @Published variables. What am I doing wrong? I'm still learning Swift. Thanks in advance for any assistance.
CodePudding user response:
According to https://developer.apple.com/documentation/corelocation/clgeocoder/1423509-geocodeaddressstring
geocodeAddressString(_:completionHandler:)
is an asynchronous function, which means its completion handler will get executed at a later point in time and the called function returns immediately.
Thus when you call
convertAddress(address: address)
it returns immediately scheduling the dispatchQueue closure to be called later.
print(String(userAddressLat)) //this prints 0.0
print(String(userAddressLong))
are executed next which prints 0.0
DispatchQueue.main.async {
self.userAddressLat = location.latitude
print(self.userAddressLat) //this prints the correct value
self.userAddressLong = location.longitude
print(self.userAddressLong) //this prints the correct value
}
are executed later.
CodePudding user response:
@lastbreath Thank you for highlighting the asynchronous nature of geocodeAddressString(_:completionHandler:)
. Because of that I found that I could use an asynchronous geocodeAddressString call in lieu of my previous approach.
This was noted in the link you provided:
func geocodeAddressString(_ addressString: String) async throws -> [CLPlacemark]
This is the fixed code...much more simplistic to achieve sending the values to @Published variable.
final class PublishMapData: ObservableObject {
@Published var userAddressLat: Double = 0.0
@Published var userAddressLong: Double = 0.0
func saveMapData(address: String){
Task {
do {
let address = "One Apple Park Way, Cupertino, CA 95014" //simulating the call from a save button for instance
try await getCoordinate(addressString: address)
print(String(userAddressLat)) //this now prints correct value
print(String(userAddressLong)) //this now prints correct value
//{...extra code here...}
//this is where I would be storing the coordinates into something like Firestore for later use
}
catch {
}
}
}
func getCoordinate(addressString : String) async throws {
let geocoder = CLGeocoder()
let placemark = try await geocoder.geocodeAddressString(addressString)
await MainActor.run(body: {
userAddressLat = placemark[0].location!.coordinate.latitude
userAddressLong = placemark[0].location!.coordinate.longitude
})
}
}
Thank you for your assistance in getting the answer.