Overview: I'm geocoding a user input address to use with mapkit and I am unable to wrap my head around using async / await to manage the delay.
I have a SwiftUI view with a textfield that calls a function onSubmit to start the geocoding process ...
TextField("Address ...", text: $searchString)
.onSubmit {
Task {
await switchToMap(address: searchString)
}
}
My switchToMap function ...
func switchToMap(address: String) async {
Task {
locationManager.convertAddress(address: address)
}
if let coordinates = locationManager.addressCoordinates {
locationManager.jumpToPoint(location: coordinates)
}else{
print("Something went wrong")
}
}
LocationManager functions (as relevant)
func getCoordinates(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
}
print(error?.localizedDescription ?? "")
completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?)
}
}
}
func convertAddress(address: String){
getCoordinates(addressString: address) { (location, error) in
if error != nil {
// Error Handler
print("Error converting address: \(error?.localizedDescription ?? "")")
return
}
DispatchQueue.main.async {
self.addressCoordinates = location
//self.jumpToPoint(location: location)
}
}
}
So, how can I make use of async / await to make all this work? I fully expect that there are several issues with this code.
Let us consider CLGeocoder
method geocodeAddressString
first. That uses a traditional completion handler pattern. To use that in an async
method, we need to wrap that in a withCheckedThrowingContinuation
:
let geocoder = CLGeocoder()
func placemarks(for string: String) async throws -> [CLPlacemark] {
try await withCheckedThrowingContinuation { continuation in
geocoder.cancelGeocode() // cancel prior one if any
geocoder.geocodeAddressString(string) { placemarks, error in
guard
error == nil,
let placemarks = placemarks?.filter( { $0.location != nil }),
!placemarks.isEmpty
else {
continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult))
return
}
continuation.resume(returning: placemarks)
}
}
}
Note, I pulled the CLGeocoder
out of the method and instead made it a property so that I could implement the “cancel prior request, if any” logic.
Also note that I am just fetching the array of CLPlacemark
rather than the coordinates, but if you want the coordinates of the first one, you can then do:
func coordinate(for string: String) async throws -> CLLocationCoordinate2D {
let placemark = try await placemarks(for: string)
.first { $0.location != nil }
guard let coordinate = placemark?.location?.coordinate else {
throw CLError(.geocodeFoundNoResult)
}
return coordinate
}
Having shown how to wrap the completion-based method in a continuation, we should note that the CLGeocoder
allows cancelation, but the above placemarks(for:)
implementation will not responded to the cancelation. To do that, we need to wrap the above in a withTaskCancellationHandler
:
func placemarks(for string: String) async throws -> [CLPlacemark] {
try await withTaskCancellationHandler {
geocoder.cancelGeocode()
} operation: {
try await withCheckedThrowingContinuation { continuation in
geocoder.cancelGeocode() // cancel prior one if any
geocoder.geocodeAddressString(string) { placemarks, error in
guard
error == nil,
let placemarks = placemarks,
!placemarks.isEmpty
else {
continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult))
return
}
continuation.resume(returning: placemarks)
}
}
}
}
You might not need cancelation at this point, but it is simple and one of those things you should probably add for any cancelable process.
For more information about refactoring old code to adopt Swift concurrency, see WWDC 2021 video Swift concurrency: Update a sample app .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.