Last active
May 13, 2025 08:25
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} | |
} | |
} |
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
Had this error "SWIFT TASK CONTINUATION MISUSE: currentLocation leaked its continuation without resuming it. This may cause tasks waiting on it to remain suspended forever." in my code so was searching how other people do it. Looks like your code has the same issue.