AsyncLocationKit

repository·main·Indexed 41 days ago

https://github.com/asyncswift/asynclocationkit

A type-safe Swift wrapper for Apple's CoreLocation framework that replaces delegate patterns and completion blocks with structured concurrency, including async/await and AsyncStream. It provides the AsyncLocationManager for requesting permissions, fetching single location updates, and monitoring continuous location streams.

Tokens
2.9K
Snippets
10
Records
13
Agent score
2%

What's inside AsyncLocationKit

  1. How AsyncLocationKit architecture works

    main

    AsyncLocationKit uses a Performer Pattern to wrap Apple's delegate-based CoreLocation API into a modern async/await interface.

    • AsyncLocationManager: The public API entry point.
    • AsyncDelegateProxy: A thread-safe dispatcher that routes CoreLocation delegate events to the appropriate performers.
    • Performers: Specialized handlers (e.g., SingleLocationUpdatePerformer, AuthorizationPerformer) that implement specific logic for different CoreLocation tasks.
    • Events: Strongly-typed enums that represent the actual data or errors received from CoreLocation.
  2. Install AsyncLocationKit via Swift Package Manager

    main

    To use AsyncLocationKit in your Swift project, add it as a dependency in your Package.swift file or via Xcode's Package Dependency interface.

    Swift Package Manager (Package.swift):

    dependencies: [
        .package(url: "https://github.com/AsyncSwift/AsyncLocationKit.git", from: "2.0.1")
    ]

    Xcode:

    1. Go to File → Add Package Dependencies.
    2. Enter the URL: https://github.com/AsyncSwift/AsyncLocationKit.git.
    3. Select version 2.0.1 or later.
  3. Migrate from delegate-based CoreLocation to AsyncLocationKit

    main

    If you are moving from a traditional CLLocationManagerDelegate pattern to AsyncLocationKit, you can replace delegate callbacks with async/await calls. Instead of implementing locationManager(_:didUpdateLocations:), you can use requestLocation() to await a single location event.

    let locationManager = AsyncLocationManager()
    
    do {
        if let event = try await locationManager.requestLocation() {
            if case .didUpdateLocations(let locations) = event {
                print(locations.first)
            }
        }
    } catch {
        print("Error: \(error)")
    }
  4. Best practices for using AsyncLocationKit

    main

    Follow these guidelines to ensure stability and battery efficiency:

    1. Initialize on the main thread: AsyncLocationManager must be initialized on the main thread to satisfy CoreLocation requirements.
    2. Check authorization first: Always verify permission status using requestPermission(with:) before attempting to request locations.
    3. Cancel tasks to stop updates: Continuous location updates are provided via streams. To stop monitoring, simply cancel the Task that is iterating over the stream.
    4. Optimize accuracy: Use lower accuracy settings (e.g., .hundredMetersAccuracy) when high precision is not required to conserve battery.
    5. Configure Info.plist: You must include the required location usage description keys in your app's Info.plist.
  5. Initialize AsyncLocationManager

    main

    The AsyncLocationManager is the main entry point for the library.

    Important: Always initialize AsyncLocationManager synchronously on the main thread.

    import AsyncLocationKit
    
    let locationManager = AsyncLocationManager(desiredAccuracy: .bestAccuracy)
  6. Configure location accuracy and background updates

    main

    You can set the initial accuracy and background update capabilities during initialization, or update them dynamically.

    Accuracy Levels:

    • .bestAccuracy
    • .nearestTenMetersAccuracy
    • .hundredMetersAccuracy
    • .kilometerAccuracy
    • .threeKilometersAccuracy
    • .bestForNavigationAccuracy

    Background Updates: Supported on iOS, macOS, and watchOS. Use allowsBackgroundLocationUpdates to enable/disable.

    // Initialization
    let manager = AsyncLocationManager(
        desiredAccuracy: .bestAccuracy,
        allowsBackgroundLocationUpdates: true
    )
    
    // Dynamic updates
    manager.updateAccuracy(with: .hundredMetersAccuracy)
    
    #if !os(tvOS)
    manager.updateAllowsBackgroundLocationUpdates(with: true)
    #endif
  7. Troubleshoot location issues

    main

    Location updates not working

    • Verify Info.plist keys are present.
    • Ensure authorization has been granted.
    • Check if location services are enabled on the device.
    • Confirm AsyncLocationManager was initialized on the main thread.

    Background location not working

    • Add "Location updates" to Background Modes in your Xcode project capabilities.
    • Set allowsBackgroundLocationUpdates to true.
    • Request .whenInUse or .always authorization (ensure you request 'Always' if background access is required).

    Task cancelled warning

    • This is expected behavior when a Task monitoring location updates is cancelled; the library handles resource cleanup automatically.
  8. Monitor continuous location updates

    main

    Use startUpdatingLocation() to receive a continuous stream of location events via an AsyncStream. You can iterate over this stream using a for await loop.

    Note: The stream automatically stops when the parent Task is cancelled.

    Task {
        for await event in await locationManager.startUpdatingLocation() {
            switch event {
            case .didUpdateLocations(let locations):
                print("New location: \(locations.last?.coordinate)")
            case .didFailWith(let error):
                print("Error: \(error)")
            case .didPaused:
                print("Location updates paused")
            case .didResume:
                print("Location updates resumed")
            }
        }
    }
    
    // To stop updates, cancel the task:
    // task.cancel()
  9. Request Location Authorization

    main

    Use requestPermission(with:) to prompt the user for location access. You can request either .whenInUsage or .always permissions.

    The method returns a CLAuthorizationStatus which you can use to handle the user's response.

    // Request "When In Use" authorization
    let status = await locationManager.requestPermission(with: .whenInUsage)
    
    // Or request "Always" authorization
    let status = await locationManager.requestPermission(with: .always)
    
    // Handle the authorization status
    switch status {
    case .authorizedWhenInUse, .authorizedAlways:
        print("Location authorized!")
    case .denied:
        print("Location access denied")
    case .restricted:
        print("Location access restricted")
    case .notDetermined:
        print("Authorization not determined")
    @unknown default:
        print("Unknown authorization status")
    }
  10. Request a single location update

    main

    To get the user's current location once, use requestLocation(). This method returns an optional LocationUpdateEvent? which contains either the updated locations or a failure error.

    do {
        if let event = try await locationManager.requestLocation() {
            switch event {
            case .didUpdateLocations(let locations):
                print("Current location: \(locations.first?.coordinate)")
            case .didFailWith(let error):
                print("Location error: \(error)")
            default:
                break
            }
        }
    } catch {
        print("Failed to get location: \(error)")
    }
  11. Reference: AsyncLocationKit Public API

    main

    The following table summarizes the primary methods available on AsyncLocationManager.

    | Method | Description | Return Type |
    |--------|-------------|-------------|
    | `requestPermission(with:)` | Request location permission | `CLAuthorizationStatus` |
    | `getAuthorizationStatus()` | Get current authorization status | `CLAuthorizationStatus` |
    | `startMonitoringAuthorization()` | Monitor authorization changes | `AuthorizationStream` |
    | `requestLocation()` | Request single location update | `LocationUpdateEvent?` |
    | `startUpdatingLocation()` | Start continuous updates | `LocationStream` |
    | `stopUpdatingLocation()` | Stop location updates | `Void` |
    | `startMonitoring(for:)` | Monitor region entry/exit | `RegionMonitoringStream` |
    | `startMonitoringVisit()` | Monitor significant visits | `VisitMonitoringStream` |
    | `startMonitoringSignificantLocationChanges()` | Monitor significant changes | `SignificantLocationChangeMonitoringStream` |
    | `getLocationEnabled()` | Check if location services enabled | `Bool` |
    | `startMonitoringLocationEnabled()` | Monitor location services status | `LocationEnabledStream` |