| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
ย | ย | |||
ย | ย | |||
ย | ย | |||
ย | ย | |||
ย | ย | |||
ย | ย | |||
ย | ย | |||
Modern, async/await wrapper for Apple's CoreLocation framework
No more delegate patterns or completion blocks. Embrace Swift's structured concurrency.
Add AsyncLocationKit to your Package.swift:
dependencies: [
.package(url: "https://github.com/AsyncSwift/AsyncLocationKit.git", from: "2.0.1")
]Or add it directly in Xcode:
Add to your Podfile:
pod 'AsyncLocationKit', :git => 'https://github.com/AsyncSwift/AsyncLocationKit.git', :tag => '2.0.1'Then run:
pod installImportant: Always initialize AsyncLocationManager synchronously on the main thread.
import AsyncLocationKit
let locationManager = AsyncLocationManager(desiredAccuracy: .bestAccuracy)// 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")
}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)")
}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()Task {
for await event in await locationManager.startMonitoringAuthorization() {
switch event {
case .didUpdate(let authorization):
print("Authorization changed to: \(authorization)")
}
}
}Task {
for await event in await locationManager.startMonitoringLocationEnabled() {
switch event {
case .didUpdate(let enabled):
print("Location services enabled: \(enabled)")
}
}
}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)")
}
}
}#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#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#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#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#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)")
}
}
#endifAsyncLocationKit 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:
This architecture provides:
let manager = AsyncLocationManager(desiredAccuracy: .bestAccuracy)
// Available accuracy levels:
// .bestAccuracy
// .nearestTenMetersAccuracy
// .hundredMetersAccuracy
// .kilometerAccuracy
// .threeKilometersAccuracy
// .bestForNavigationAccuracy
// Update accuracy dynamically:
manager.updateAccuracy(with: .hundredMetersAccuracy)let manager = AsyncLocationManager(
desiredAccuracy: .bestAccuracy,
allowsBackgroundLocationUpdates: true
)
// Update background setting dynamically (iOS/macOS/watchOS only):
#if !os(tvOS)
manager.updateAllowsBackgroundLocationUpdates(with: true)
#endif| Method | Description | Return Type |
|---|---|---|
| requestPermission(with:) | Request location permission | CLAuthorizationStatus |
| getAuthorizationStatus() | Get current authorization status | CLAuthorizationStatus |
| startMonitoringAuthorization() | Monitor authorization changes | AuthorizationStream |
| Method | Description | Return Type |
|---|---|---|
| requestLocation() | Request single location update | LocationUpdateEvent? |
| startUpdatingLocation() | Start continuous updates | LocationStream |
| stopUpdatingLocation() | Stop location updates | Void |
| Method | Description | Return Type |
|---|---|---|
| startMonitoring(for:) | Monitor region entry/exit | RegionMonitoringStream |
| startMonitoringVisit() | Monitor significant visits | VisitMonitoringStream |
| startMonitoringSignificantLocationChanges() | Monitor significant changes | SignificantLocationChangeMonitoringStream |
| Method | Description | Return Type |
|---|---|---|
| getLocationEnabled() | Check if location services enabled | Bool |
| startMonitoringLocationEnabled() | Monitor location services status | LocationEnabledStream |
| Feature | iOS | macOS | watchOS | tvOS |
|---|---|---|---|---|
| Basic Location | โ | โ | โ | โ |
| Authorization | โ | โ | โ | โ |
| Region Monitoring | โ | โ | โ | โ |
| Visit Monitoring | โ | โ | โ | โ |
| Heading Updates | โ | โ | โ | โ |
| Beacon Ranging | โ | โ | โ | โ |
| Significant Changes | โ | โ | โ | โ |
| Background Updates | โ | โ | โ | โ |
Before (delegate pattern):
class LocationManager: NSObject, CLLocationManagerDelegate {
let manager = CLLocationManager()
var completion: ((CLLocation?) -> Void)?
override init() {
super.init()
manager.delegate = self
}
func requestLocation() {
manager.requestLocation()
}
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
completion?(locations.first)
}
}After (async/await):
let locationManager = AsyncLocationManager()
do {
if let event = try await locationManager.requestLocation() {
if case .didUpdateLocations(let locations) = event {
print(locations.first)
}
}
} catch {
print("Error: \(error)")
}Always initialize on the main thread: CoreLocation requires main thread initialization
let manager = AsyncLocationManager() // โ
On main threadHandle authorization properly: Always check authorization before requesting location
let status = await manager.requestPermission(with: .whenInUsage)
guard status == .authorizedWhenInUse || status == .authorizedAlways else {
return
}Cancel tasks to stop monitoring: Streams automatically clean up when tasks are cancelled
let task = Task {
for await event in await manager.startUpdatingLocation() { ... }
}
// Later:
task.cancel() // Stops location updatesChoose appropriate accuracy: Use lower accuracy when possible to save battery
let manager = AsyncLocationManager(desiredAccuracy: .hundredMetersAccuracy)Add required Info.plist keys: Don't forget to add location usage descriptions
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show nearby places</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location to provide location-based features</string>This is expected behavior when you cancel a Task that's monitoring location updates. The library properly cleans up resources.
Background location not workingContributions are welcome! Please feel free to submit a Pull Request. For major changes:
AsyncLocationKit is released under the MIT License. See LICENSE for details.
MIT License Copyright (c) 2022 AsyncSwift Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Made with โค๏ธ by the AsyncSwift team
| Back | FazBrowse Home | New Git URL |