Home > Net >  How do I get the @Published variable to update properly in Swift?
How do I get the @Published variable to update properly in Swift?

Time:01-14

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.

  • Related