diff --git a/README.md b/README.md index 793504d..e69dff6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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) [![Swift](https://img.shields.io/badge/Swift-5.5-orange?style=flat)](https://img.shields.io/badge/Swift-5.5-Orange?style=flat) -[![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) +[![Platforms](https://img.shields.io/badge/platforms-iOS--13%20|%20macOS(beta)%20|%20watchOS--6(beta)%20|%20tvOS(beta)-orange?style=flat)](https://img.shields.io/badge/platforms-iOS--13%20|%20macOS(beta)%20|%20watchOS--6(beta)%20|%20tvOS(beta)-orange?style=flat) Wrapper for Apple `CoreLocation` framework with new Concurency Model. No more `delegate` pattern or `completion blocks`. diff --git a/Sources/AsyncLocationKit/AsyncLocationManager.swift b/Sources/AsyncLocationKit/AsyncLocationManager.swift index 224d8e3..74a090d 100644 --- a/Sources/AsyncLocationKit/AsyncLocationManager.swift +++ b/Sources/AsyncLocationKit/AsyncLocationManager.swift @@ -34,6 +34,7 @@ public typealias HeadingMonitorStream = AsyncStream public typealias AuthorizationStream = AsyncStream public typealias AccuracyAuthorizationStream = AsyncStream @available(watchOS, unavailable) +@available(tvOS, unavailable) public typealias BeaconsRangingStream = AsyncStream public final class AsyncLocationManager { @@ -51,7 +52,9 @@ public final class AsyncLocationManager { locationDelegate = LocationDelegate(delegateProxy: proxyDelegate) self.locationManager.delegate = locationDelegate self.locationManager.desiredAccuracy = desiredAccuracy.convertingAccuracy + #if !os(tvOS) self.locationManager.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates + #endif } @@ -64,7 +67,7 @@ public final class AsyncLocationManager { @available(watchOS 6.0, *) public func getAuthorizationStatus() -> CLAuthorizationStatus { - if #available(iOS 14, watchOS 7, *) { + if #available(iOS 14, tvOS 14, watchOS 7, *) { return locationManager.authorizationStatus } else { return CLLocationManager.authorizationStatus() @@ -116,7 +119,7 @@ public final class AsyncLocationManager { proxyDelegate.cancel(for: AccuracyAuthorizationMonitoringPerformer.self) } - @available(iOS 14, watchOS 7, *) + @available(iOS 14, tvOS 14, watchOS 7, *) public func getAccuracyAuthorization() -> CLAccuracyAuthorization { locationManager.accuracyAuthorization } @@ -125,6 +128,7 @@ public final class AsyncLocationManager { locationManager.desiredAccuracy = newAccuracy.convertingAccuracy } + @available(tvOS, unavailable) public func updateAllowsBackgroundLocationUpdates(with newAllows: Bool) { locationManager.allowsBackgroundLocationUpdates = newAllows } @@ -149,7 +153,7 @@ public final class AsyncLocationManager { }) } -#if !APPCLIP +#if !APPCLIP && !os(tvOS) @available(*, deprecated, message: "Use new function requestPermission(with:)") @available(watchOS 7.0, *) @available(iOS 14, *) @@ -195,11 +199,12 @@ public final class AsyncLocationManager { } } - @available(iOS 14, watchOS 7, *) + @available(iOS 14, tvOS 14, watchOS 7, *) public func requestTemporaryFullAccuracyAuthorization(purposeKey: String) async throws -> CLAccuracyAuthorization? { try await locationPermissionTemporaryFullAccuracy(purposeKey: purposeKey) } + @available(tvOS, unavailable) public func startUpdatingLocation() async -> LocationStream { let monitoringPerformer = MonitoringUpdateLocationPerformer() return LocationStream { streamContinuation in @@ -231,6 +236,7 @@ public final class AsyncLocationManager { } @available(watchOS, unavailable) + @available(tvOS, unavailable) public func startMonitoring(for region: CLRegion) async -> RegionMonitoringStream { let performer = RegionMonitoringPerformer(region: region) return RegionMonitoringStream { streamContinuation in @@ -244,6 +250,7 @@ public final class AsyncLocationManager { } @available(watchOS, unavailable) + @available(tvOS, unavailable) public func stopMonitoring(for region: CLRegion) { proxyDelegate.cancel(for: RegionMonitoringPerformer.self) { regionMonitoring in guard let regionPerformer = regionMonitoring as? RegionMonitoringPerformer else { return false } @@ -253,6 +260,7 @@ public final class AsyncLocationManager { } @available(watchOS, unavailable) + @available(tvOS, unavailable) public func startMonitoringVisit() async -> VisitMonitoringStream { let performer = VisitMonitoringPerformer() return VisitMonitoringStream { stream in @@ -266,6 +274,7 @@ public final class AsyncLocationManager { } @available(watchOS, unavailable) + @available(tvOS, unavailable) public func stopMonitoringVisit() { proxyDelegate.cancel(for: VisitMonitoringPerformer.self) locationManager.stopMonitoringVisits() @@ -292,6 +301,7 @@ public final class AsyncLocationManager { #endif @available(watchOS, unavailable) + @available(tvOS, unavailable) public func startRangingBeacons(satisfying: CLBeaconIdentityConstraint) async -> BeaconsRangingStream { let performer = BeaconsRangePerformer(satisfying: satisfying) return BeaconsRangingStream { stream in @@ -305,6 +315,7 @@ public final class AsyncLocationManager { } @available(watchOS, unavailable) + @available(tvOS, unavailable) public func stopRangingBeacons(satisfying: CLBeaconIdentityConstraint) { proxyDelegate.cancel(for: BeaconsRangePerformer.self) { beaconsMonitoring in guard let beaconsPerformer = beaconsMonitoring as? BeaconsRangePerformer else { return false } @@ -346,12 +357,14 @@ extension AsyncLocationManager { locationManager.requestAlwaysAuthorization() } #else - if #available(iOS 14, watchOS 7, *), locationManager.authorizationStatus != .notDetermined && locationManager.authorizationStatus != .authorizedWhenInUse { + if #available(iOS 14, tvOS 14, watchOS 7, *), locationManager.authorizationStatus != .notDetermined && locationManager.authorizationStatus != .authorizedWhenInUse { continuation.resume(with: .success(locationManager.authorizationStatus)) } else { + #if !os(tvOS) authorizationPerformer.linkContinuation(continuation) proxyDelegate.addPerformer(authorizationPerformer) locationManager.requestAlwaysAuthorization() + #endif } #endif } @@ -360,7 +373,7 @@ extension AsyncLocationManager { }) } - @available(iOS 14, watchOS 7, *) + @available(iOS 14, tvOS 14, watchOS 7, *) private func locationPermissionTemporaryFullAccuracy(purposeKey: String) async throws -> CLAccuracyAuthorization? { let authorizationPerformer = RequestAccuracyAuthorizationPerformer() return try await withTaskCancellationHandler(operation: { diff --git a/Sources/AsyncLocationKit/CoreLocationEvents.swift b/Sources/AsyncLocationKit/CoreLocationEvents.swift index d209706..81358d8 100644 --- a/Sources/AsyncLocationKit/CoreLocationEvents.swift +++ b/Sources/AsyncLocationKit/CoreLocationEvents.swift @@ -31,15 +31,19 @@ enum CoreLocationDelegateEvent { case didChangeAccuracyAuthorization(authorization: CLAccuracyAuthorization) // MARK: - Location events case didUpdate(locations: [CLLocation]) + @available(tvOS, unavailable) case didUpdateHeading(heading: CLHeading) @available(watchOS, unavailable) + @available(tvOS, unavailable) case didDetermine(state: CLRegionState, forRegion: CLRegion) // MARK: - Beacons events @available(watchOS, unavailable) + @available(tvOS, unavailable) case didRange(beacons: [CLBeacon], beaconConstraint: CLBeaconIdentityConstraint) @available(watchOS, unavailable) + @available(tvOS, unavailable) case didFailRanginFor(beaconConstraint: CLBeaconIdentityConstraint, error: Error) // MARK: - Region events case didEnterRegion(region: CLRegion) @@ -50,6 +54,7 @@ enum CoreLocationDelegateEvent { case monitoringDidFailFor(region: CLRegion?, error: Error) // MARK: - Visit event @available(watchOS, unavailable) + @available(tvOS, unavailable) case didVisit(visit: CLVisit) // MARK: - Pause and resume events case locationUpdatesPaused diff --git a/Sources/AsyncLocationKit/Helpers/NotificationNames.swift b/Sources/AsyncLocationKit/Helpers/NotificationNames.swift index 27ede74..b3377ff 100644 --- a/Sources/AsyncLocationKit/Helpers/NotificationNames.swift +++ b/Sources/AsyncLocationKit/Helpers/NotificationNames.swift @@ -21,7 +21,7 @@ // SOFTWARE. import Foundation -#if os(iOS) +#if os(iOS) || os(tvOS) import UIKit #elseif os(macOS) import AppKit @@ -31,7 +31,7 @@ import WatchKit @available(macOS 12, iOS 13, tvOS 13, watchOS 8, *) struct NotificationNamesConstants { -#if os(iOS) +#if os(iOS) || os(tvOS) static let willResignActiveName = UIApplication.willResignActiveNotification #elseif os(macOS) static let willResignActiveName = NSApplication.willResignActiveNotification @@ -39,7 +39,7 @@ struct NotificationNamesConstants { static let willResignActiveName = WKExtension.applicationWillResignActiveNotification #endif -#if os(iOS) +#if os(iOS) || os(tvOS) static let didBecomeActiveName = UIApplication.didBecomeActiveNotification #elseif os(macOS) static let didBecomeActiveName = NSApplication.didBecomeActiveNotification diff --git a/Sources/AsyncLocationKit/LocationDelegate.swift b/Sources/AsyncLocationKit/LocationDelegate.swift index a05626d..20a332e 100644 --- a/Sources/AsyncLocationKit/LocationDelegate.swift +++ b/Sources/AsyncLocationKit/LocationDelegate.swift @@ -33,7 +33,7 @@ internal class LocationDelegate: NSObject, CLLocationManagerDelegate { // MARK: - Authorize @available(watchOS 7.0, *) - @available(iOS 14, *) + @available(iOS 14, tvOS 14, *) func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { proxy?.eventForMethodInvoked(.didChangeAuthorization(status: manager.authorizationStatus)) proxy?.eventForMethodInvoked(.didChangeAccuracyAuthorization(authorization: manager.accuracyAuthorization)) @@ -59,11 +59,13 @@ internal class LocationDelegate: NSObject, CLLocationManagerDelegate { proxy?.eventForMethodInvoked(.didUpdate(locations: locations)) } + #if !os(tvOS) func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { proxy?.eventForMethodInvoked(.didUpdateHeading(heading: newHeading)) } + #endif - #if os(iOS) || os(tvOS) + #if os(iOS) func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) { proxy?.eventForMethodInvoked(.didDetermine(state: state, forRegion: region)) } @@ -96,7 +98,7 @@ internal class LocationDelegate: NSObject, CLLocationManagerDelegate { proxy?.eventForMethodInvoked(.didFailWithError(error: error)) } - #if os(iOS) || os(tvOS) + #if os(iOS) func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) { proxy?.eventForMethodInvoked(.monitoringDidFailFor(region: region, error: error)) } diff --git a/Sources/AsyncLocationKit/Performers/BeaconsRangePerformer.swift b/Sources/AsyncLocationKit/Performers/BeaconsRangePerformer.swift index 3b0e599..b733168 100644 --- a/Sources/AsyncLocationKit/Performers/BeaconsRangePerformer.swift +++ b/Sources/AsyncLocationKit/Performers/BeaconsRangePerformer.swift @@ -24,6 +24,7 @@ import Foundation import CoreLocation @available(watchOS, unavailable) +@available(tvOS, unavailable) public enum BeaconRangeEvent { case didRange(beacons: [CLBeacon], beaconConstraint: CLBeaconIdentityConstraint) case didFailRanginFor(beaconConstraint: CLBeaconIdentityConstraint, error: Error) @@ -42,17 +43,21 @@ class BeaconsRangePerformer: AnyLocationPerformer { var eventssupported: [CoreLocationEventSupport] = [.didRangeBeacons, .didFailRanginForBeaconConstraint] @available(watchOS, unavailable) + @available(tvOS, unavailable) var satisfying: CLBeaconIdentityConstraint @available(watchOS, unavailable) + @available(tvOS, unavailable) var stream: BeaconsRangingStream.Continuation? @available(watchOS, unavailable) + @available(tvOS, unavailable) init(satisfying: CLBeaconIdentityConstraint) { self.satisfying = satisfying } @available(watchOS, unavailable) + @available(tvOS, unavailable) func linkContinuation(_ continuation: BeaconsRangingStream.Continuation) { self.stream = continuation } @@ -63,10 +68,12 @@ class BeaconsRangePerformer: AnyLocationPerformer { func invokedMethod(event: CoreLocationDelegateEvent) { switch event { + #if !os(tvOS) case .didRange(let beacons, let beaconConstraint): stream?.yield(.didRange(beacons: beacons, beaconConstraint: beaconConstraint)) case .didFailRanginFor(let beaconConstraint, let error): stream?.yield(.didFailRanginFor(beaconConstraint: beaconConstraint, error: error)) + #endif default: fatalError("Method can't be execute by this performer: \(String(describing: self)) for event: \(type(of: event))") } diff --git a/Sources/AsyncLocationKit/Performers/HeadingMonitorPerformer.swift b/Sources/AsyncLocationKit/Performers/HeadingMonitorPerformer.swift index 77566c9..2206076 100644 --- a/Sources/AsyncLocationKit/Performers/HeadingMonitorPerformer.swift +++ b/Sources/AsyncLocationKit/Performers/HeadingMonitorPerformer.swift @@ -24,6 +24,7 @@ import Foundation import CoreLocation.CLHeading public enum HeadingMonitorEvent { + @available(tvOS, unavailable) case didUpdate(heading: CLHeading) case didFailWith(error: Error) } @@ -49,8 +50,10 @@ class HeadingMonitorPerformer: AnyLocationPerformer { func invokedMethod(event: CoreLocationDelegateEvent) { switch event { + #if !os(tvOS) case .didUpdateHeading(let heading): stream?.yield(.didUpdate(heading: heading)) + #endif case .didFailWithError(let error): stream?.yield(.didFailWith(error: error)) default: diff --git a/Sources/AsyncLocationKit/Performers/VisitMonitoringPerformer.swift b/Sources/AsyncLocationKit/Performers/VisitMonitoringPerformer.swift index 93b1b77..661a6d7 100644 --- a/Sources/AsyncLocationKit/Performers/VisitMonitoringPerformer.swift +++ b/Sources/AsyncLocationKit/Performers/VisitMonitoringPerformer.swift @@ -25,6 +25,7 @@ import CoreLocation.CLVisit public enum VisitMonitoringEvent { @available(watchOS, unavailable) + @available(tvOS, unavailable) case didVisit(visit: CLVisit) case didFailWithError(error: Error) } @@ -53,7 +54,7 @@ class VisitMonitoringPerformer: AnyLocationPerformer { case .didFailWithError(let error): stream?.yield(.didFailWithError(error: error)) case .didVisit(let visit): - #if os(iOS) || os(tvOS) + #if os(iOS) stream?.yield(.didVisit(visit: visit)) #endif default: diff --git a/Tests/AsyncLocationKitTests/AsyncLocationKitTests.swift b/Tests/AsyncLocationKitTests/AsyncLocationKitTests.swift index 2f6dd6c..7489ea7 100644 --- a/Tests/AsyncLocationKitTests/AsyncLocationKitTests.swift +++ b/Tests/AsyncLocationKitTests/AsyncLocationKitTests.swift @@ -16,6 +16,7 @@ final class AsyncLocationKitTests: XCTestCase { } func testAllowsBackgroundLocationUpdates() { + #if !os(tvOS) let firstAllows = true let locationManager = AsyncLocationManager(locationManager: AsyncLocationKitTests.mockLocationManager, allowsBackgroundLocationUpdates: firstAllows) XCTAssertTrue(AsyncLocationKitTests.mockLocationManager.allowsBackgroundLocationUpdates == firstAllows) @@ -23,6 +24,7 @@ final class AsyncLocationKitTests: XCTestCase { let secondAllows = false locationManager.updateAllowsBackgroundLocationUpdates(with: secondAllows) XCTAssertTrue(AsyncLocationKitTests.mockLocationManager.allowsBackgroundLocationUpdates == secondAllows) + #endif } func testRequestLocation() async { diff --git a/Tests/AsyncLocationKitTests/LocationManager.swift b/Tests/AsyncLocationKitTests/LocationManager.swift index 5c0f35c..1739d0f 100644 --- a/Tests/AsyncLocationKitTests/LocationManager.swift +++ b/Tests/AsyncLocationKitTests/LocationManager.swift @@ -11,6 +11,7 @@ import CoreLocation class MockLocationManager: CLLocationManager { private var mockAllowsBackgroundLocationUpdates: Bool = false + #if !os(tvOS) override var allowsBackgroundLocationUpdates: Bool { get { return mockAllowsBackgroundLocationUpdates @@ -19,6 +20,7 @@ class MockLocationManager: CLLocationManager { mockAllowsBackgroundLocationUpdates = newValue } } + #endif override var location: CLLocation? { return CLLocation(latitude: 100, longitude: 200)