Skip to content

Commit

Permalink
Merge pull request #38 from RomanPodymov/main
Browse files Browse the repository at this point in the history
Support tvOS
  • Loading branch information
gre4ixin authored Jun 21, 2023
2 parents e70b5dc + 554a775 commit 2112449
Show file tree
Hide file tree
Showing 10 changed files with 49 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
25 changes: 19 additions & 6 deletions Sources/AsyncLocationKit/AsyncLocationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public typealias HeadingMonitorStream = AsyncStream<HeadingMonitorEvent>
public typealias AuthorizationStream = AsyncStream<AuthorizationEvent>
public typealias AccuracyAuthorizationStream = AsyncStream<AccuracyAuthorizationEvent>
@available(watchOS, unavailable)
@available(tvOS, unavailable)
public typealias BeaconsRangingStream = AsyncStream<BeaconRangeEvent>

public final class AsyncLocationManager {
Expand All @@ -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
}


Expand All @@ -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()
Expand Down Expand Up @@ -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
}
Expand All @@ -125,6 +128,7 @@ public final class AsyncLocationManager {
locationManager.desiredAccuracy = newAccuracy.convertingAccuracy
}

@available(tvOS, unavailable)
public func updateAllowsBackgroundLocationUpdates(with newAllows: Bool) {
locationManager.allowsBackgroundLocationUpdates = newAllows
}
Expand All @@ -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, *)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 }
Expand All @@ -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
Expand All @@ -266,6 +274,7 @@ public final class AsyncLocationManager {
}

@available(watchOS, unavailable)
@available(tvOS, unavailable)
public func stopMonitoringVisit() {
proxyDelegate.cancel(for: VisitMonitoringPerformer.self)
locationManager.stopMonitoringVisits()
Expand All @@ -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
Expand All @@ -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 }
Expand Down Expand Up @@ -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
}
Expand All @@ -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: {
Expand Down
5 changes: 5 additions & 0 deletions Sources/AsyncLocationKit/CoreLocationEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions Sources/AsyncLocationKit/Helpers/NotificationNames.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// SOFTWARE.

import Foundation
#if os(iOS)
#if os(iOS) || os(tvOS)
import UIKit
#elseif os(macOS)
import AppKit
Expand All @@ -31,15 +31,15 @@ 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
#elseif os(watchOS)
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
Expand Down
8 changes: 5 additions & 3 deletions Sources/AsyncLocationKit/LocationDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
}
Expand Down Expand Up @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand All @@ -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))")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Foundation
import CoreLocation.CLHeading

public enum HeadingMonitorEvent {
@available(tvOS, unavailable)
case didUpdate(heading: CLHeading)
case didFailWith(error: Error)
}
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import CoreLocation.CLVisit

public enum VisitMonitoringEvent {
@available(watchOS, unavailable)
@available(tvOS, unavailable)
case didVisit(visit: CLVisit)
case didFailWithError(error: Error)
}
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions Tests/AsyncLocationKitTests/AsyncLocationKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ 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)

let secondAllows = false
locationManager.updateAllowsBackgroundLocationUpdates(with: secondAllows)
XCTAssertTrue(AsyncLocationKitTests.mockLocationManager.allowsBackgroundLocationUpdates == secondAllows)
#endif
}

func testRequestLocation() async {
Expand Down
2 changes: 2 additions & 0 deletions Tests/AsyncLocationKitTests/LocationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CoreLocation
class MockLocationManager: CLLocationManager {
private var mockAllowsBackgroundLocationUpdates: Bool = false

#if !os(tvOS)
override var allowsBackgroundLocationUpdates: Bool {
get {
return mockAllowsBackgroundLocationUpdates
Expand All @@ -19,6 +20,7 @@ class MockLocationManager: CLLocationManager {
mockAllowsBackgroundLocationUpdates = newValue
}
}
#endif

override var location: CLLocation? {
return CLLocation(latitude: 100, longitude: 200)
Expand Down

0 comments on commit 2112449

Please sign in to comment.