SkillAgentSearch skills...

AsyncLocationKit

📍async/await CoreLocation

Install / Use

/learn @AsyncSwift/AsyncLocationKit

README

AsyncLocationKit

<p align="center"> <img src="https://img.shields.io/badge/Swift-5.5+-orange.svg" alt="Swift 5.5+"/> <img src="https://img.shields.io/badge/iOS-13.0+-blue.svg" alt="iOS 13.0+"/> <img src="https://img.shields.io/badge/macOS-12.0+-blue.svg" alt="macOS 12.0+"/> <img src="https://img.shields.io/badge/watchOS-6.0+-blue.svg" alt="watchOS 6.0+"/> <img src="https://img.shields.io/badge/tvOS-13.0+-blue.svg" alt="tvOS 13.0+"/> <img src="https://img.shields.io/badge/license-MIT-black.svg" alt="License: MIT"/> <img src="https://img.shields.io/badge/SPM-compatible-green.svg" alt="SPM Compatible"/> <img src="https://img.shields.io/badge/CocoaPods-compatible-green.svg" alt="CocoaPods Compatible"/> </p> <p align="center"> <b>Modern, async/await wrapper for Apple's CoreLocation framework</b><br/> No more delegate patterns or completion blocks. Embrace Swift's structured concurrency. </p>

Features

  • Async/Await Native: Built from the ground up for Swift's modern concurrency model
  • AsyncStream Support: Monitor continuous location updates with for await loops
  • Type-Safe: Comprehensive event types for all CoreLocation scenarios
  • Zero Dependencies: Pure Swift, no external frameworks
  • Multi-Platform: iOS, macOS, watchOS, and tvOS support
  • Thread-Safe: Concurrent access protected with serial dispatch queues
  • Swift 6 Ready: Full Sendable conformance for strict concurrency checking

Installation

Swift Package Manager

Add AsyncLocationKit to your Package.swift:

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

Or add it directly in Xcode:

  1. File → Add Package Dependencies
  2. Enter: https://github.com/AsyncSwift/AsyncLocationKit.git
  3. Select version 2.0.0 or later

CocoaPods

Add to your Podfile:

pod 'AsyncLocationKit', :git => 'https://github.com/AsyncSwift/AsyncLocationKit.git', :tag => '2.0.0'

Then run:

pod install

Quick Start

Initialization

Important: Always initialize AsyncLocationManager synchronously on the main thread.

import AsyncLocationKit

let locationManager = AsyncLocationManager(desiredAccuracy: .bestAccuracy)

Request Authorization

// 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")
}

Single Location Request

Get the user's location once:

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)")
}

Continuous Location Updates

Monitor location changes using AsyncStream:

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")
        }
    }
}

The stream automatically stops when the Task is cancelled:

let task = Task {
    for await event in await locationManager.startUpdatingLocation() {
        // Handle location updates
    }
}

// Later, cancel the task to stop location updates
task.cancel()

Usage Examples

Monitor Authorization Changes

Task {
    for await event in await locationManager.startMonitoringAuthorization() {
        switch event {
        case .didUpdate(let authorization):
            print("Authorization changed to: \(authorization)")
        }
    }
}

Monitor Location Services Availability

Task {
    for await event in await locationManager.startMonitoringLocationEnabled() {
        switch event {
        case .didUpdate(let enabled):
            print("Location services enabled: \(enabled)")
        }
    }
}

Region Monitoring (iOS/macOS only)

let region = CLCircularRegion(
    center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
    radius: 100,
    identifier: "San Francisco"
)

Task {
    for await event in await locationManager.startMonitoring(for: region) {
        switch event {
        case .didEnterTo(let region):
            print("Entered region: \(region.identifier)")
        case .didExitTo(let region):
            print("Exited region: \(region.identifier)")
        case .didStartMonitoringFor(let region):
            print("Started monitoring: \(region.identifier)")
        case .monitoringDidFailFor(let region, let error):
            print("Monitoring failed: \(error)")
        }
    }
}

Heading Updates (iOS only)

#if os(iOS)
Task {
    for await event in await locationManager.startUpdatingHeading() {
        switch event {
        case .didUpdate(let heading):
            print("Heading: \(heading.trueHeading)°")
        case .didFailWith(let error):
            print("Heading error: \(error)")
        }
    }
}
#endif

Visit Monitoring (iOS only)

#if os(iOS)
Task {
    for await event in await locationManager.startMonitoringVisit() {
        switch event {
        case .didVisit(let visit):
            print("Visit: \(visit.coordinate)")
            print("Arrival: \(visit.arrivalDate)")
            print("Departure: \(visit.departureDate)")
        case .didFailWithError(let error):
            print("Visit monitoring error: \(error)")
        }
    }
}
#endif

Beacon Ranging (iOS/macOS only)

#if !os(watchOS) && !os(tvOS)
let beaconConstraint = CLBeaconIdentityConstraint(
    uuid: UUID(uuidString: "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0")!
)

Task {
    for await event in await locationManager.startRangingBeacons(satisfying: beaconConstraint) {
        switch event {
        case .didRange(let beacons, _):
            print("Found \(beacons.count) beacons")
        case .didFailRanginFor(_, let error):
            print("Beacon ranging failed: \(error)")
        }
    }
}
#endif

Significant Location Changes (iOS/macOS only)

#if !os(watchOS) && !os(tvOS)
Task {
    for await event in await locationManager.startMonitoringSignificantLocationChanges() {
        switch event {
        case .didUpdateLocations(let locations):
            print("Significant location change: \(locations)")
        case .didFailWith(let error):
            print("Error: \(error)")
        case .didPaused, .didResume:
            break
        }
    }
}
#endif

Request Temporary Full Accuracy (iOS 14+)

#if os(iOS)
if #available(iOS 14.0, *) {
    do {
        let accuracy = try await locationManager.requestTemporaryFullAccuracyAuthorization(
            purposeKey: "YourPurposeKeyFromInfoPlist"
        )
        print("Accuracy authorization: \(accuracy)")
    } catch {
        print("Failed to request full accuracy: \(error)")
    }
}
#endif

Architecture

AsyncLocationKit uses a Performer Pattern to elegantly manage the complex delegate-based CoreLocation API:

┌─────────────────────────┐
│  AsyncLocationManager   │
│  (Public API)           │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│  AsyncDelegateProxy     │
│  (Event Dispatcher)     │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│  AnyLocationPerformer   │
│  (Protocol)             │
└───────────┬─────────────┘
            │
            ├──► SingleLocationUpdatePerformer
            ├──► MonitoringUpdateLocationPerformer
            ├──► AuthorizationPerformer
            ├──► RegionMonitoringPerformer
            └──► ...and more

Key Components:

  • AsyncLocationManager: Main entry point providing async/await methods
  • AsyncDelegateProxy: Thread-safe dispatcher routing events to performers
  • Performers: Individual handlers for specific CoreLocation delegate methods
  • Events: Strongly-typed enums representing CoreLocation callbacks

This architecture provides:

  • Thread-safe concurrent access
  • Clean separation of concerns
  • Easy testability
  • Type safety throughout

Configuration Options

Location Accuracy

let manager = AsyncLocationManager(desiredAccuracy: .bestAccuracy)

// Available accuracy levels:
// .bestAccuracy
// .nearestTenMetersAccuracy
// .hundredMetersAccuracy
// .kilometerAccuracy
// .threeKilometersAccuracy
// .bestForNavigationAccuracy

// Update accuracy dynamically:
manager.updateAccuracy(with: .hundredMetersAccuracy)

Background Location Updates

let manager = AsyncLocationManager(
    desiredAccuracy: .bestAccuracy,
    allowsBackgroundLocationUpdates: true
)

// Update background setting dynamically (iOS/macOS/watchOS only):
#if !os(tvOS)
manager.updateAllowsBackgroundLocationUpdates(with: true)
#endif

API Reference

Location Authorization

| Method | Description | Return Type | |--------|-------------|-------------| | requestPermission(with:) | Request location permission | CLAuthorizationStatus | | getAuthorizationStatus() | Get current author

Related Skills

View on GitHub
GitHub Stars198
CategoryDevelopment
Updated19d ago
Forks28

Languages

Swift

Security Score

100/100

Audited on Mar 4, 2026

No findings