Updating the User’s Location with Core Location and Swift Concurrency in SwiftUI

Updating the User’s Location with Core Location and Swift Concurrency in SwiftUI

Learn how to create an asynchronous API to access Core Location on a SwiftUI app.

You can create your own wrappers to manage access to iOS core services, taking advantage of the features of the Swift language to create modern and flexible APIs for your application.

One of the services you can work with is Core Location, using Swift Concurrency to create a simple and elegant approach to accessing the user location. With a CheckedContinuation object, we can interface between synchronous and asynchronous code, so we can update the location of the user once the location manager can retrieve it using delegation.

Creating the Location Manager

Create a LocationManager class that inherits from the NSObject class and conforms to the CLLocationManagerDelegate protocol. Add a private property called locationManager, a CLLocationManager object that will provide us access to the location services of the device.

Also, override the default initializer so we can assign the delegate to the locationManager object to be the LocationManager class.

class LocationManager: NSObject, CLLocationManagerDelegate {
    //MARK: Object to Access Location Services
    private let locationManager = CLLocationManager()
    
    //MARK: Set up the Location Manager Delegate
    override init() {
        super.init()
        locationManager.delegate = self
    }
}

Declaration of the LocationManager class

Requesting Authorization

To be able to access the current location of the user you must ask for authorization to use the location services of the device.

Requesting authorization to use location services | Apple Developer Documentation
Obtain authorization to use location services and manage changes to your app’s authorization status.

Apple Developer Documentation for requesting authorization to use location services

On the project settings, you must go into the Info area and add to the Custom iOS Target properties the key Privacy - Location When In Use Usage Description and a string value with a message explaining why your application needs access to the location services.

Project File → Project Target → "Info" tab → Custom iOS Target Properties

With that done, create a short method to check if the location manager's authorisation is not determined and request it if that’s the case.

class LocationManager: NSObject, CLLocationManagerDelegate {

    ...

    //MARK: Request Authorization to access the User Location
    func checkAuthorization() {
        switch locationManager.authorizationStatus {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        default:
            return
        }
    }
}

Method to request user authorization to access the location services

Getting the Current Location

Now to get the user's current location create a property that will store an CheckedContinuation object that, when resumed, returns an CLLocation object or throws an error.

class LocationManager: NSObject, CLLocationManagerDelegate {
    
    private let locationManager = CLLocationManager()

    ...

    //MARK: Continuation Object for the User Location
    private var continuation: CheckedContinuation<CLLocation, Error>?

}

Setting up the continuation object that will return the user location once it is available

Create a computed property named currentLocation that will asynchronously return the result of the continuation object once it is resolved. If everything goes well we will receive an CLLocation object, if not an error will be thrown.

The continuation property is set with the continuation object provided by the withCheckedThrowingContinuation(function:_:) method in its body closure, and then we use the locationManager to request a one-time delivery of the user’s current location.

class LocationManager: NSObject, CLLocationManagerDelegate {
    
    private let locationManager = CLLocationManager()

    ...

    private var continuation: CheckedContinuation<CLLocation, Error>?
    
    //MARK: Asynchronously request the current location
    var currentLocation: CLLocation {
        get async throws {
            return try await withCheckedThrowingContinuation { continuation in
                // 1. Set up the continuation object
                self.continuation = continuation
                // 2. Triggers the update of the current location
                locationManager.requestLocation()
            }
        }
    }
}

Property that returns the current location of the user asynchronously

The last step is to implement the CLLocationManagerDelegate methods that are triggered when the requestLocation() method is called. Provide an implementation for a successful case, resuming the continuation object returning the current user’s location, and a case where it fails to provide the user’s location, resuming the continuation object by throwing the error object associated with it.

class LocationManager: NSObject, CLLocationManagerDelegate {
    
    ...
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // 4. If there is a location available
        if let lastLocation = locations.last {
            // 5. Resumes the continuation object with the user location as result
            continuation?.resume(returning: lastLocation)
            // Resets the continuation object
            continuation = nil
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        // 6. If not possible to retrieve a location, resumes with an error
        continuation?.resume(throwing: error)
        // Resets the continuation object
        continuation = nil
    }

}

Location manager delegate methods to retrieve the user location

To see an example of how to use the location manager in a SwiftUI view to display the current user location on a Map check the following code snippet on GitHub.

Log in for full access to resources and code snippets.