0

With coordinates from the locationManager I would like to show a map with the location (address) at the bottom obtained through reverse geoLocation. The coordinates and map are displaying correctly, but I am having trouble generating the address. I am trying to follow the example code at Find city name and country from latitude and longitude in Swift in the section iOS 11 or later. The two extensions (CLPlacemark and CLLocation) shown in the example are identical to what I am using. So it appears that although I am following the example usage I am not handling the asynchronous Placemark function correctly. The function getLocation() is correctly displaying the address, but it is not getting back to saveButton().

Any help with the asynchronous function return will be appreciated.

struct EntryView: View {
    @Environment(\.managedObjectContext) var viewContext    // core data
    @ObservedObject private var lm = LocationManager()      // location

    @State private var entryLat: Double = 0.0
    @State private var entryLong: Double = 0.0
    @State private var addr: String = ""
    
    var body: some View {
        
        GeometryReader { g in
            
            List {  

              Button(action: {
                    self.saveButton()   // save entry button pressed
                }) {
                    HStack {
                        Spacer()
                        Text ("Save")
                        Spacer()
                    }
                }
        }
        .navigationBarHidden(true)
        .navigationViewStyle(StackNavigationViewStyle())
    }

    // the save button has been pressed
    func saveButton() {
        
        // get coordinates and address
        let addr = getLocation()
        print("addr = \(addr)")  // nothing displayed here except addr
        
        // save entry to core data
        let newEntry = CurrTrans(context: viewContext)
        
        newEntry.id = UUID()                            
        newEntry.entryDT = entryDT         // entry date
        newEntry.entryDsc = entryDsc       // entry description                      
        newEntry.moneyD = moneyD           // money as double
        
        newEntry.entryLat = entryLat       // store location for maps
        newEntry.entryLong = entryLong
        
        newEntry.address = addr           // formatted address
        print("newEntry.address = \(newEntry.address ?? "")")  
        
        do {
            try viewContext.save()
            
        } catch {
            print(error.localizedDescription)
        }
    }

    func getLocation() -> String {
 
        // get location coordinates
        let result = lm.getLocationCoordinates()
        entryLat = result.0
        entryLong = result.1
        
        // get location address
        let location = CLLocation(latitude: entryLat, longitude: entryLong)
        location.placemark { placemark, error in
            guard let placemark = placemark else {
                print("Error:", error ?? "nil")
                return
            }
            
            print("formatted address: \(placemark.postalAddressFormatted ?? "")")
            addr = placemark.postalAddressFormatted ?? "Unknown"
            return addr
        }
    }
}

The code below is part of the location manager.

extension CLLocation {
    func placemark(completion: @escaping (_ placemark: CLPlacemark?, _ error: Error?) -> ()) {
        CLGeocoder().reverseGeocodeLocation(self) { completion($0?.first, $1) }
    }
}

extension CLPlacemark {
    /// street name, eg. Infinite Loop
    var streetName: String? { thoroughfare }
    /// // eg. 1
    var streetNumber: String? { subThoroughfare }
    /// city, eg. Cupertino
    var city: String? { locality }
    /// neighborhood, common name, eg. Mission District
    var neighborhood: String? { subLocality }
    /// state, eg. CA
    var state: String? { administrativeArea }
    /// county, eg. Santa Clara
    var county: String? { subAdministrativeArea }
    /// zip code, eg. 95014
    var zipCode: String? { postalCode }
    /// postal address formatted
   
    var postalAddressFormatted: String? {
        guard let postalAddress = postalAddress else { return nil }
        return CNPostalAddressFormatter().string(from: postalAddress)
    }
}
Galen Smith
  • 299
  • 2
  • 14

1 Answers1

1

Your code, as is, doesn't compile. The compiler gives a warning about Unexpected non-void return value in void function when you try to return addr` because the closure has a non-void return type.

Instead, use a completion handler.

This might look like this:

func getLocation(completion: @escaping (String) -> Void) {
    // get location coordinates
    let result = lm.getLocationCoordinates()
    entryLat = result.0
    entryLong = result.1
    
    // get location address
    let location = CLLocation(latitude: entryLat, longitude: entryLong)
    location.placemark { placemark, error in
        guard let placemark = placemark else {
            print("Error:", error ?? "nil")
            return
        }
        
        print("formatted address: \(placemark.postalAddressFormatted ?? "")")
        addr = placemark.postalAddressFormatted ?? "Unknown"
        completion(addr)
    }
}

And, earlier, in saveButton:

func saveButton() {
    
    // get coordinates and address
    getLocation { addr in
        print("addr = \(addr)")
        self.addr = addr
        //do your CoreData code that depends on addr here
    }
    //don't try to use addr here, outside the completion handler
}

You also have a missing } in your body

jnpdx
  • 45,847
  • 6
  • 64
  • 94