0

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.

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
Cslim
  • 303
  • 2
  • 8
  • `convertAddress` is asynchronous, add a completion handler like in `getCoordinate` or run the code you want to run inside `convertAddress` – vadian Jan 13 '23 at 21:23

2 Answers2

1

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.

lastbreath
  • 383
  • 1
  • 11
  • Thank you for the information. This is very insightful; however, how would you recommend adjusting the original code to get the @Published variables to be filled with the correct values? – Cslim Jan 13 '23 at 21:42
0

@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.

Cslim
  • 303
  • 2
  • 8