Skip to content

Instantly share code, notes, and snippets.

@runys
Last active May 13, 2025 08:25
Show Gist options
  • Save runys/10a01deb2b7182c674823b2d051ad271 to your computer and use it in GitHub Desktop.
Save runys/10a01deb2b7182c674823b2d051ad271 to your computer and use it in GitHub Desktop.
Location Manager service providing an asynchronous way of accessing the user current location.
import Foundation
import CoreLocation
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
}
//MARK: Request Authorization to access the User Location
func checkAuthorization() {
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
default:
return
}
}
//MARK: Continuation Object for the User Location
private var continuation: CheckedContinuation<CLLocation, Error>?
// Error messages associated with the location manager
enum LocationManagerError: String, Error {
case replaceContinuation = "Continuation replaced."
case locationNotFound = "No location found."
}
//MARK: Async Request the Current Location
var currentLocation: CLLocation {
get async throws {
// Check if there is a continuation being worked on
if self.continuation != nil {
// If so, resumes it throwing an error
self.continuation?.resume(throwing: LocationManagerError.replaceContinuation)
// And deletes it, so a new one can be created
self.continuation = nil
}
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()
}
}
}
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
} else {
// If there is no location, resumes the continuation throwing and error to avoid a continuation leak
continuation?.resume(throwing: LocationManagerError.locationNotFound)
}
}
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
}
}
import SwiftUI
import MapKit
struct ContentView: View {
// MARK: Instance of the location manager
var locationManager = LocationManager()
// MARK: Properties
@State var location: CLLocation?
// MARK: Position of the MapCamera
@State private var position: MapCameraPosition = .automatic
// MARK: Body
var body: some View {
VStack {
Map(position: $position) {
UserAnnotation()
}
.clipShape(RoundedRectangle(cornerRadius: 15))
.shadow(radius: 5)
Text(location?.description ?? "No location yet")
.padding()
.foregroundColor(.secondary)
Button {
Task { await self.updateLocation() }
} label: {
Text("Get Location")
}
}
.padding()
.background {
Color.gray.opacity(0.4)
.ignoresSafeArea()
}
.task {
// 1. Check if the app is authorized to access the location services of the device
locationManager.checkAuthorization()
}
}
// MARK: Get the current user location if available
func updateLocation() async {
do {
// 1. Get the current location from the location manager
self.location = try await locationManager.currentLocation
// 2. Update the camera position of the map to center around the user location
self.updateMapPosition()
} catch {
print("Could not get user location: \(error.localizedDescription)")
}
}
// MARK: Change the camera of the Map view
func updateMapPosition() {
if let location = self.location {
let regionCenter = CLLocationCoordinate2D(
latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude
)
let regionSpan = MKCoordinateSpan(latitudeDelta: 0.125, longitudeDelta: 0.125)
self.position = .region(MKCoordinateRegion(center: regionCenter, span: regionSpan))
}
}
}
@runys
Copy link
Author

runys commented May 13, 2025

Solved! I've updated the location manager so a continuation is always resumed, in every code path. Thanks for the feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment