The APIs to get the current location are a bit awkward. I've never liked callback APIs, so I wrap them in proper async calls whenever I find them.
Using a continuation is the first step, and it gives you an async method to call. However, this just hangs indefinitely if you have the misfortune of calling it on a non-UI thread.
To get a proper version, you have to ensure it's executed on the main thread. This is what I came up with:
import Foundation
import CoreLocation
@MainActor
class OneTimeGeoLocationUtil: NSObject {
static func getLocation() async -> CLLocationCoordinate2D? {
let oneTimeLocationService = GeolocServiceInternal()
return await oneTimeLocationService.getLocation()
}
}
private class GeolocServiceInternal: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
private var continuation: CheckedContinuation<CLLocationCoordinate2D?, Never>?
override init() {
super.init()
manager.delegate = self
}
func getLocation() async -> CLLocationCoordinate2D? {
if !CLLocationManager.locationServicesEnabled() {
return nil
}
return await withCheckedContinuation { continuation2 in
continuation = continuation2
manager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
continuation?.resume(returning: location.coordinate)
continuation = nil
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// Log the error
continuation?.resume(returning: nil)
continuation = nil
}
}
A one-time use class instance holds the continuation and hosts the callback functions, exposing the raw async API. Then a wrapper static function with @MainActor
makes it easier to call and ensures requestLocation()
is executed on the main thread.
Top comments (0)