Skip to content

Commit 3532761

Browse files
authored
Merge pull request #26 from AndrewDodd42/main
WatchOS Support
2 parents c5dc87b + 21c0d14 commit 3532761

14 files changed

+438
-12
lines changed

AsyncLocationKit.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Pod::Spec.new do |s|
99
s.source = { :git => 'https://github.com/AsyncSwift/AsyncLocationKit.git', :tag => s.version }
1010
s.ios.deployment_target = '13.0'
1111
s.tvos.deployment_target = '13.0'
12+
s.watchos.deployment_target = '6.0'
1213
s.requires_arc = true
1314
s.swift_version = '5.5'
1415
s.source_files = 'Sources/AsyncLocationKit/**/*.{swift}'

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ let package = Package(
77
name: "AsyncLocationKit",
88
platforms: [
99
.iOS("13.0"),
10-
.macOS(.v12)
10+
.macOS(.v12),
11+
.watchOS(.v6)
1112
],
1213

1314
products: [

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat)](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat)
44
[![Swift](https://img.shields.io/badge/Swift-5.5-orange?style=flat)](https://img.shields.io/badge/Swift-5.5-Orange?style=flat)
5-
[![Platforms](https://img.shields.io/badge/platforms-iOS--13%20|%20macOS(beta)-orange?style=flat)](https://img.shields.io/badge/platforms-iOS--13%20|%20macOS(beta)-orange?style=flat)
5+
[![Platforms](https://img.shields.io/badge/platforms-iOS--13%20|%20macOS(beta)%20|%20watchOS--6(beta)-orange?style=flat)](https://img.shields.io/badge/platforms-iOS--13%20|%20macOS(beta)-orange?style=flat)
66

77
Wrapper for Apple `CoreLocation` framework with new Concurency Model. No more `delegate` pattern or `completion blocks`.
88

Sources/AsyncLocationKit/AsyncLocationManager.swift

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,44 +24,114 @@ import Foundation
2424
import CoreLocation
2525

2626
public typealias AuthotizationContinuation = CheckedContinuation<CLAuthorizationStatus, Never>
27+
public typealias AccuracyAuthorizationContinuation = CheckedContinuation<CLAccuracyAuthorization?, Never>
2728
public typealias LocationOnceContinuation = CheckedContinuation<LocationUpdateEvent?, Error>
29+
public typealias LocationEnabledStream = AsyncStream<LocationEnabledEvent>
2830
public typealias LocationStream = AsyncStream<LocationUpdateEvent>
2931
public typealias RegionMonitoringStream = AsyncStream<RegionMonitoringEvent>
3032
public typealias VisitMonitoringStream = AsyncStream<VisitMonitoringEvent>
3133
public typealias HeadingMonitorStream = AsyncStream<HeadingMonitorEvent>
34+
public typealias AuthorizationStream = AsyncStream<AuthorizationEvent>
35+
public typealias AccuracyAuthorizationStream = AsyncStream<AccuracyAuthorizationEvent>
36+
@available(watchOS, unavailable)
3237
public typealias BeaconsRangingStream = AsyncStream<BeaconRangeEvent>
3338

3439
public final class AsyncLocationManager {
3540
private var locationManager: CLLocationManager
3641
private var proxyDelegate: AsyncDelegateProxyInterface
3742
private var locationDelegate: CLLocationManagerDelegate
3843

39-
public convenience init(desiredAccuracy: LocationAccuracy = .bestAccuracy) {
44+
public convenience init(desiredAccuracy: LocationAccuracy = .bestAccuracy, allowsBackgroundLocationUpdates: Bool = false) {
4045
self.init(locationManager: CLLocationManager(), desiredAccuracy: desiredAccuracy)
4146
}
4247

43-
public init(locationManager: CLLocationManager, desiredAccuracy: LocationAccuracy = .bestAccuracy) {
48+
public init(locationManager: CLLocationManager, desiredAccuracy: LocationAccuracy = .bestAccuracy, allowsBackgroundLocationUpdates: Bool = false) {
4449
self.locationManager = locationManager
4550
proxyDelegate = AsyncDelegateProxy()
4651
locationDelegate = LocationDelegate(delegateProxy: proxyDelegate)
4752
self.locationManager.delegate = locationDelegate
4853
self.locationManager.desiredAccuracy = desiredAccuracy.convertingAccuracy
54+
self.locationManager.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates
4955
}
5056

5157

58+
public func getLocationEnabled() async -> Bool {
59+
// Though undocumented, `locationServicesEnabled()` must not be called from the main thread. Otherwise,
60+
// we get a runtime warning "This method can cause UI unresponsiveness if invoked on the main thread"
61+
// Therefore, we use `Task.detached` to ensure we're off the main thread.
62+
// Also, we force `try` as we expect no exceptions to be thrown from `locationServicesEnabled()`
63+
try! await Task.detached { CLLocationManager.locationServicesEnabled() }.value
64+
}
65+
66+
@available(watchOS 6.0, *)
5267
public func getAuthorizationStatus() -> CLAuthorizationStatus {
53-
if #available(iOS 14, *) {
68+
if #available(iOS 14, watchOS 7, *) {
5469
return locationManager.authorizationStatus
5570
} else {
5671
return CLLocationManager.authorizationStatus()
5772
}
5873
}
59-
74+
75+
public func startMonitoringLocationEnabled() async -> LocationEnabledStream {
76+
let performer = LocationEnabledMonitoringPerformer()
77+
return LocationEnabledStream { stream in
78+
performer.linkContinuation(stream)
79+
proxyDelegate.addPerformer(performer)
80+
stream.onTermination = { @Sendable _ in
81+
self.stopMonitoringLocationEnabled()
82+
}
83+
}
84+
}
85+
86+
public func stopMonitoringLocationEnabled() {
87+
proxyDelegate.cancel(for: LocationEnabledMonitoringPerformer.self)
88+
}
89+
90+
public func startMonitoringAuthorization() async -> AuthorizationStream {
91+
let performer = AuthorizationMonitoringPerformer()
92+
return AuthorizationStream { stream in
93+
performer.linkContinuation(stream)
94+
proxyDelegate.addPerformer(performer)
95+
stream.onTermination = { @Sendable _ in
96+
self.stopMonitoringAuthorization()
97+
}
98+
}
99+
}
100+
101+
public func stopMonitoringAuthorization() {
102+
proxyDelegate.cancel(for: AuthorizationMonitoringPerformer.self)
103+
}
104+
105+
public func startMonitoringAccuracyAuthorization() async -> AccuracyAuthorizationStream {
106+
let performer = AccuracyAuthorizationMonitoringPerformer()
107+
return AccuracyAuthorizationStream { stream in
108+
performer.linkContinuation(stream)
109+
proxyDelegate.addPerformer(performer)
110+
stream.onTermination = { @Sendable _ in
111+
self.stopMonitoringAccuracyAuthorization()
112+
}
113+
}
114+
}
115+
116+
public func stopMonitoringAccuracyAuthorization() {
117+
proxyDelegate.cancel(for: AccuracyAuthorizationMonitoringPerformer.self)
118+
}
119+
120+
@available(iOS 14, watchOS 7, *)
121+
public func getAccuracyAuthorization() -> CLAccuracyAuthorization {
122+
locationManager.accuracyAuthorization
123+
}
124+
60125
public func updateAccuracy(with newAccuracy: LocationAccuracy) {
61126
locationManager.desiredAccuracy = newAccuracy.convertingAccuracy
62127
}
63-
128+
129+
public func updateAllowsBackgroundLocationUpdates(with newAllows: Bool) {
130+
locationManager.allowsBackgroundLocationUpdates = newAllows
131+
}
132+
64133
@available(*, deprecated, message: "Use new function requestPermission(with:)")
134+
@available(watchOS 7.0, *)
65135
public func requestAuthorizationWhenInUse() async -> CLAuthorizationStatus {
66136
let authorizationPerformer = RequestAuthorizationPerformer()
67137
return await withTaskCancellationHandler(operation: {
@@ -82,24 +152,37 @@ public final class AsyncLocationManager {
82152

83153
#if !APPCLIP
84154
@available(*, deprecated, message: "Use new function requestPermission(with:)")
155+
@available(watchOS 7.0, *)
156+
@available(iOS 14, *)
85157
public func requestAuthorizationAlways() async -> CLAuthorizationStatus {
86158
let authorizationPerformer = RequestAuthorizationPerformer()
87159
return await withTaskCancellationHandler(operation: {
88160
await withCheckedContinuation { continuation in
161+
#if os(macOS)
89162
if #available(iOS 14, *), locationManager.authorizationStatus != .notDetermined {
90163
continuation.resume(with: .success(locationManager.authorizationStatus))
91164
} else {
92165
authorizationPerformer.linkContinuation(continuation)
93166
proxyDelegate.addPerformer(authorizationPerformer)
94167
locationManager.requestAlwaysAuthorization()
95168
}
169+
#else
170+
if #available(iOS 14, *), locationManager.authorizationStatus != .notDetermined && locationManager.authorizationStatus != .authorizedWhenInUse {
171+
continuation.resume(with: .success(locationManager.authorizationStatus))
172+
} else {
173+
authorizationPerformer.linkContinuation(continuation)
174+
proxyDelegate.addPerformer(authorizationPerformer)
175+
locationManager.requestAlwaysAuthorization()
176+
}
177+
#endif
96178
}
97179
}, onCancel: {
98180
proxyDelegate.cancel(for: authorizationPerformer.uniqueIdentifier)
99181
})
100182
}
101183
#endif
102184

185+
@available(watchOS 7.0, *)
103186
public func requestPermission(with permissionType: LocationPermission) async -> CLAuthorizationStatus {
104187
switch permissionType {
105188
case .always:
@@ -112,7 +195,12 @@ public final class AsyncLocationManager {
112195
return await locationPermissionWhenInUse()
113196
}
114197
}
115-
198+
199+
@available(iOS 14, watchOS 7, *)
200+
public func requestTemporaryFullAccuracyAuthorization(purposeKey: String) async -> CLAccuracyAuthorization? {
201+
await locationPermissionTemporaryFullAccuracy(purposeKey: purposeKey)
202+
}
203+
116204
public func startUpdatingLocation() async -> LocationStream {
117205
let monitoringPerformer = MonitoringUpdateLocationPerformer()
118206
return LocationStream { streamContinuation in
@@ -143,6 +231,7 @@ public final class AsyncLocationManager {
143231
})
144232
}
145233

234+
@available(watchOS, unavailable)
146235
public func startMonitoring(for region: CLRegion) async -> RegionMonitoringStream {
147236
let performer = RegionMonitoringPerformer(region: region)
148237
return RegionMonitoringStream { streamContinuation in
@@ -154,6 +243,7 @@ public final class AsyncLocationManager {
154243
}
155244
}
156245

246+
@available(watchOS, unavailable)
157247
public func stopMonitoring(for region: CLRegion) {
158248
proxyDelegate.cancel(for: RegionMonitoringPerformer.self) { regionMonitoring in
159249
guard let regionPerformer = regionMonitoring as? RegionMonitoringPerformer else { return false }
@@ -162,6 +252,7 @@ public final class AsyncLocationManager {
162252
locationManager.stopMonitoring(for: region)
163253
}
164254

255+
@available(watchOS, unavailable)
165256
public func startMonitoringVisit() async -> VisitMonitoringStream {
166257
let performer = VisitMonitoringPerformer()
167258
return VisitMonitoringStream { stream in
@@ -174,6 +265,7 @@ public final class AsyncLocationManager {
174265
}
175266
}
176267

268+
@available(watchOS, unavailable)
177269
public func stopMonitoringVisit() {
178270
proxyDelegate.cancel(for: VisitMonitoringPerformer.self)
179271
locationManager.stopMonitoringVisits()
@@ -199,6 +291,7 @@ public final class AsyncLocationManager {
199291
}
200292
#endif
201293

294+
@available(watchOS, unavailable)
202295
public func startRangingBeacons(satisfying: CLBeaconIdentityConstraint) async -> BeaconsRangingStream {
203296
let performer = BeaconsRangePerformer(satisfying: satisfying)
204297
return BeaconsRangingStream { stream in
@@ -211,6 +304,7 @@ public final class AsyncLocationManager {
211304
}
212305
}
213306

307+
@available(watchOS, unavailable)
214308
public func stopRangingBeacons(satisfying: CLBeaconIdentityConstraint) {
215309
proxyDelegate.cancel(for: BeaconsRangePerformer.self) { beaconsMonitoring in
216310
guard let beaconsPerformer = beaconsMonitoring as? BeaconsRangePerformer else { return false }
@@ -243,13 +337,45 @@ extension AsyncLocationManager {
243337
let authorizationPerformer = RequestAuthorizationPerformer()
244338
return await withTaskCancellationHandler(operation: {
245339
await withCheckedContinuation { continuation in
246-
if #available(iOS 14, *), locationManager.authorizationStatus != .notDetermined {
340+
#if os(macOS)
341+
if #available(iOS 14, watchOS 7, *), locationManager.authorizationStatus != .notDetermined {
342+
continuation.resume(with: .success(locationManager.authorizationStatus))
343+
} else {
344+
authorizationPerformer.linkContinuation(continuation)
345+
proxyDelegate.addPerformer(authorizationPerformer)
346+
locationManager.requestAlwaysAuthorization()
347+
}
348+
#else
349+
if #available(iOS 14, watchOS 7, *), locationManager.authorizationStatus != .notDetermined && locationManager.authorizationStatus != .authorizedWhenInUse {
247350
continuation.resume(with: .success(locationManager.authorizationStatus))
248351
} else {
249352
authorizationPerformer.linkContinuation(continuation)
250353
proxyDelegate.addPerformer(authorizationPerformer)
251354
locationManager.requestAlwaysAuthorization()
252355
}
356+
#endif
357+
}
358+
}, onCancel: {
359+
proxyDelegate.cancel(for: authorizationPerformer.uniqueIdentifier)
360+
})
361+
}
362+
363+
@available(iOS 14, watchOS 7, *)
364+
private func locationPermissionTemporaryFullAccuracy(purposeKey: String) async -> CLAccuracyAuthorization? {
365+
let authorizationPerformer = RequestAccuracyAuthorizationPerformer()
366+
return await withTaskCancellationHandler(operation: {
367+
await withCheckedContinuation { continuation in
368+
if locationManager.authorizationStatus != .notDetermined && locationManager.accuracyAuthorization == .fullAccuracy {
369+
continuation.resume(with: .success(locationManager.accuracyAuthorization))
370+
} else if locationManager.authorizationStatus == .notDetermined {
371+
continuation.resume(with: .success(nil))
372+
} else if !CLLocationManager.locationServicesEnabled() {
373+
continuation.resume(with: .success(nil))
374+
} else {
375+
authorizationPerformer.linkContinuation(continuation)
376+
proxyDelegate.addPerformer(authorizationPerformer)
377+
locationManager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: purposeKey)
378+
}
253379
}
254380
}, onCancel: {
255381
proxyDelegate.cancel(for: authorizationPerformer.uniqueIdentifier)

Sources/AsyncLocationKit/CoreLocationEvents.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,20 @@ import CoreLocation
2626

2727
enum CoreLocationDelegateEvent {
2828
// MARK: - Authorization event
29+
case didChangeLocationEnabled(enabled: Bool)
2930
case didChangeAuthorization(status: CLAuthorizationStatus)
31+
case didChangeAccuracyAuthorization(authorization: CLAccuracyAuthorization)
3032
// MARK: - Location events
3133
case didUpdate(locations: [CLLocation])
3234
case didUpdateHeading(heading: CLHeading)
35+
36+
@available(watchOS, unavailable)
3337
case didDetermine(state: CLRegionState, forRegion: CLRegion)
38+
3439
// MARK: - Beacons events
40+
@available(watchOS, unavailable)
3541
case didRange(beacons: [CLBeacon], beaconConstraint: CLBeaconIdentityConstraint)
42+
@available(watchOS, unavailable)
3643
case didFailRanginFor(beaconConstraint: CLBeaconIdentityConstraint, error: Error)
3744
// MARK: - Region events
3845
case didEnterRegion(region: CLRegion)
@@ -42,15 +49,20 @@ enum CoreLocationDelegateEvent {
4249
case didFailWithError(error: Error)
4350
case monitoringDidFailFor(region: CLRegion?, error: Error)
4451
// MARK: - Visit event
52+
@available(watchOS, unavailable)
4553
case didVisit(visit: CLVisit)
4654
// MARK: - Pause and resume events
4755
case locationUpdatesPaused
4856
case locationUpdatesResume
4957

5058
func rawEvent() -> CoreLocationEventSupport {
5159
switch self {
60+
case .didChangeLocationEnabled(_):
61+
return .didChangeLocationEnabled
5262
case .didChangeAuthorization(_):
5363
return .didChangeAuthorization
64+
case .didChangeAccuracyAuthorization(_):
65+
return .didChangeAccuracyAuthorization
5466
case .didUpdate(_):
5567
return .didUpdateLocations
5668
case .didUpdateHeading(_):
@@ -83,7 +95,9 @@ enum CoreLocationDelegateEvent {
8395

8496
/// Event for mark what support current delegate
8597
enum CoreLocationEventSupport {
98+
case didChangeLocationEnabled
8699
case didChangeAuthorization
100+
case didChangeAccuracyAuthorization
87101
case didUpdateLocations
88102
case didUpdateHeading
89103
case didDetermineState

0 commit comments

Comments
 (0)