diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ComposableCoreLocation.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ComposableCoreLocation.xcscheme
deleted file mode 100644
index 97ba321..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/ComposableCoreLocation.xcscheme
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ComposableCoreLocation_watchOS.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ComposableCoreLocation_watchOS.xcscheme
deleted file mode 100644
index f8c322e..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/ComposableCoreLocation_watchOS.xcscheme
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Examples/LocationManager/Common/AppCore.swift b/Examples/LocationManager/Common/AppCore.swift
index 0e4356a..84e424b 100644
--- a/Examples/LocationManager/Common/AppCore.swift
+++ b/Examples/LocationManager/Common/AppCore.swift
@@ -1,5 +1,5 @@
import ComposableArchitecture
-import ComposableCoreLocation
+import CoreLocationClient
import MapKit
public struct PointOfInterest: Equatable, Hashable {
diff --git a/Examples/LocationManager/Common/LocalSearchClient/Client.swift b/Examples/LocationManager/Common/LocalSearchClient/Client.swift
index c37868b..f281ddc 100644
--- a/Examples/LocationManager/Common/LocalSearchClient/Client.swift
+++ b/Examples/LocationManager/Common/LocalSearchClient/Client.swift
@@ -2,21 +2,21 @@ import MapKit
import Dependencies
extension DependencyValues {
- public var localSearchClient: LocalSearchClient {
- get { self[LocalSearchClient.self] }
- set { self[LocalSearchClient.self] = newValue }
- }
+ public var localSearchClient: LocalSearchClient {
+ get { self[LocalSearchClient.self] }
+ set { self[LocalSearchClient.self] = newValue }
+ }
}
extension LocalSearchClient: TestDependencyKey {
- public static let previewValue = Self.noop
- public static let testValue = Self.failing
+ public static let previewValue = Self.noop
+ public static let testValue = Self.noop
}
extension LocalSearchClient {
- public static let noop = Self(
- search: { _ in try await Task.never() }
- )
+ public static let noop = Self(
+ search: { _ in try await Task.never() }
+ )
}
public struct LocalSearchClient {
diff --git a/Examples/LocationManager/Common/LocalSearchClient/Live.swift b/Examples/LocationManager/Common/LocalSearchClient/Live.swift
index 79780d6..392e677 100644
--- a/Examples/LocationManager/Common/LocalSearchClient/Live.swift
+++ b/Examples/LocationManager/Common/LocalSearchClient/Live.swift
@@ -1,9 +1,6 @@
-import Combine
import ComposableArchitecture
import MapKit
-
-
extension LocalSearchClient: DependencyKey {
public static let liveValue = Self(
search: { request in
diff --git a/Examples/LocationManager/CommonTests/CommonTests.swift b/Examples/LocationManager/CommonTests/CommonTests.swift
index 286215b..b81e0fa 100644
--- a/Examples/LocationManager/CommonTests/CommonTests.swift
+++ b/Examples/LocationManager/CommonTests/CommonTests.swift
@@ -1,6 +1,6 @@
import Combine
import ComposableArchitecture
-import ComposableCoreLocation
+import CoreLocationClient
import CoreLocation
import MapKit
import XCTest
diff --git a/Examples/LocationManager/Desktop/LocationManagerView.swift b/Examples/LocationManager/Desktop/LocationManagerView.swift
index 7f1446f..0691ab7 100644
--- a/Examples/LocationManager/Desktop/LocationManagerView.swift
+++ b/Examples/LocationManager/Desktop/LocationManagerView.swift
@@ -1,6 +1,6 @@
import Combine
import ComposableArchitecture
-import ComposableCoreLocation
+import CoreLocationClient
import MapKit
import SwiftUI
diff --git a/Examples/LocationManager/LocationManager.xcodeproj/project.pbxproj b/Examples/LocationManager/LocationManager.xcodeproj/project.pbxproj
index 8aea16a..8f00d33 100644
--- a/Examples/LocationManager/LocationManager.xcodeproj/project.pbxproj
+++ b/Examples/LocationManager/LocationManager.xcodeproj/project.pbxproj
@@ -7,22 +7,20 @@
objects = {
/* Begin PBXBuildFile section */
+ 6A102C722CF36059006E5DB6 /* CoreLocationClient in Frameworks */ = {isa = PBXBuildFile; productRef = 6A102C712CF36059006E5DB6 /* CoreLocationClient */; };
+ 6A102C742CF36066006E5DB6 /* CoreLocationClient in Frameworks */ = {isa = PBXBuildFile; productRef = 6A102C732CF36066006E5DB6 /* CoreLocationClient */; };
CA17CC0F24720BBA00BDDF11 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA17CC0E24720BBA00BDDF11 /* SceneDelegate.swift */; };
CA17CC1124720BBA00BDDF11 /* LocationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA17CC1024720BBA00BDDF11 /* LocationManagerView.swift */; };
CA17CC1324720BBB00BDDF11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CA17CC1224720BBB00BDDF11 /* Assets.xcassets */; };
CA17CC3924720BEB00BDDF11 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = CA17CC3824720BEB00BDDF11 /* ComposableArchitecture */; };
- CA17CCB62474582000BDDF11 /* ComposableCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = CA17CCB52474582000BDDF11 /* ComposableCoreLocation */; };
CA17CCC024745B1000BDDF11 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA17CCBF24745B1000BDDF11 /* AppDelegate.swift */; };
CA17CCC224745B1000BDDF11 /* LocationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA17CCC124745B1000BDDF11 /* LocationManagerView.swift */; };
CA17CCC424745B1000BDDF11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CA17CCC324745B1000BDDF11 /* Assets.xcassets */; };
CA17CCE324745B1A00BDDF11 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = CA17CCE224745B1A00BDDF11 /* ComposableArchitecture */; };
- CA17CCE524745B1A00BDDF11 /* ComposableCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = CA17CCE424745B1A00BDDF11 /* ComposableCoreLocation */; };
CA74947D2474A4F100B13019 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946B2474A4F100B13019 /* MapView.swift */; };
CA74947E2474A4F100B13019 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946B2474A4F100B13019 /* MapView.swift */; };
CA74947F2474A4F100B13019 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946C2474A4F100B13019 /* Helpers.swift */; };
CA7494802474A4F100B13019 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946C2474A4F100B13019 /* Helpers.swift */; };
- CA7494812474A4F100B13019 /* Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946E2474A4F100B13019 /* Failing.swift */; };
- CA7494822474A4F100B13019 /* Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946E2474A4F100B13019 /* Failing.swift */; };
CA7494832474A4F100B13019 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946F2474A4F100B13019 /* Client.swift */; };
CA7494842474A4F100B13019 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA74946F2474A4F100B13019 /* Client.swift */; };
CA7494852474A4F100B13019 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA7494702474A4F100B13019 /* Models.swift */; };
@@ -67,7 +65,6 @@
CA17CCCC24745B1000BDDF11 /* LocationManagerDesktop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LocationManagerDesktop.entitlements; sourceTree = ""; };
CA74946B2474A4F100B13019 /* MapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; };
CA74946C2474A4F100B13019 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; };
- CA74946E2474A4F100B13019 /* Failing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Failing.swift; sourceTree = ""; };
CA74946F2474A4F100B13019 /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; };
CA7494702474A4F100B13019 /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; };
CA7494712474A4F100B13019 /* Live.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Live.swift; sourceTree = ""; };
@@ -85,8 +82,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- CA17CCB62474582000BDDF11 /* ComposableCoreLocation in Frameworks */,
CA17CC3924720BEB00BDDF11 /* ComposableArchitecture in Frameworks */,
+ 6A102C722CF36059006E5DB6 /* CoreLocationClient in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -94,8 +91,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- CA17CCE524745B1A00BDDF11 /* ComposableCoreLocation in Frameworks */,
CA17CCE324745B1A00BDDF11 /* ComposableArchitecture in Frameworks */,
+ 6A102C742CF36066006E5DB6 /* CoreLocationClient in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -185,7 +182,6 @@
CA74946D2474A4F100B13019 /* LocalSearchClient */ = {
isa = PBXGroup;
children = (
- CA74946E2474A4F100B13019 /* Failing.swift */,
CA74946F2474A4F100B13019 /* Client.swift */,
CA7494702474A4F100B13019 /* Models.swift */,
CA7494712474A4F100B13019 /* Live.swift */,
@@ -220,7 +216,7 @@
name = LocationManagerMobile;
packageProductDependencies = (
CA17CC3824720BEB00BDDF11 /* ComposableArchitecture */,
- CA17CCB52474582000BDDF11 /* ComposableCoreLocation */,
+ 6A102C712CF36059006E5DB6 /* CoreLocationClient */,
);
productName = LocationManager;
productReference = CA17CC0924720BBA00BDDF11 /* LocationManagerMobile.app */;
@@ -241,7 +237,7 @@
name = LocationManagerDesktop;
packageProductDependencies = (
CA17CCE224745B1A00BDDF11 /* ComposableArchitecture */,
- CA17CCE424745B1A00BDDF11 /* ComposableCoreLocation */,
+ 6A102C732CF36066006E5DB6 /* CoreLocationClient */,
);
productName = LocationManagerDesktop;
productReference = CA17CCBD24745B1000BDDF11 /* LocationManagerDesktop.app */;
@@ -374,7 +370,6 @@
files = (
CA7494892474A4F100B13019 /* AppCore.swift in Sources */,
CA17CC0F24720BBA00BDDF11 /* SceneDelegate.swift in Sources */,
- CA7494812474A4F100B13019 /* Failing.swift in Sources */,
CA7494832474A4F100B13019 /* Client.swift in Sources */,
CA74947D2474A4F100B13019 /* MapView.swift in Sources */,
CA17CC1124720BBA00BDDF11 /* LocationManagerView.swift in Sources */,
@@ -390,7 +385,6 @@
files = (
CA74948A2474A4F100B13019 /* AppCore.swift in Sources */,
CA17CCC224745B1000BDDF11 /* LocationManagerView.swift in Sources */,
- CA7494822474A4F100B13019 /* Failing.swift in Sources */,
CA7494842474A4F100B13019 /* Client.swift in Sources */,
CA74947E2474A4F100B13019 /* MapView.swift in Sources */,
CA17CCC024745B1000BDDF11 /* AppDelegate.swift in Sources */,
@@ -785,21 +779,21 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
- CA17CC3824720BEB00BDDF11 /* ComposableArchitecture */ = {
+ 6A102C712CF36059006E5DB6 /* CoreLocationClient */ = {
isa = XCSwiftPackageProductDependency;
- productName = ComposableArchitecture;
+ productName = CoreLocationClient;
};
- CA17CCB52474582000BDDF11 /* ComposableCoreLocation */ = {
+ 6A102C732CF36066006E5DB6 /* CoreLocationClient */ = {
isa = XCSwiftPackageProductDependency;
- productName = ComposableCoreLocation;
+ productName = CoreLocationClient;
};
- CA17CCE224745B1A00BDDF11 /* ComposableArchitecture */ = {
+ CA17CC3824720BEB00BDDF11 /* ComposableArchitecture */ = {
isa = XCSwiftPackageProductDependency;
productName = ComposableArchitecture;
};
- CA17CCE424745B1A00BDDF11 /* ComposableCoreLocation */ = {
+ CA17CCE224745B1A00BDDF11 /* ComposableArchitecture */ = {
isa = XCSwiftPackageProductDependency;
- productName = ComposableCoreLocation;
+ productName = ComposableArchitecture;
};
/* End XCSwiftPackageProductDependency section */
};
diff --git a/Examples/LocationManager/LocationManager.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/LocationManager/LocationManager.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index bb3407f..ba91c61 100644
--- a/Examples/LocationManager/LocationManager.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Examples/LocationManager/LocationManager.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,12 +1,13 @@
{
+ "originHash" : "958a4d0fa159e09bc62a61c84c233f6e7100c3292b86267844720ec627c0a056",
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
- "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb",
- "version" : "1.0.0"
+ "revision" : "9fa31f4403da54855f1e2aeaeff478f4f0e40b13",
+ "version" : "1.0.2"
}
},
{
@@ -23,8 +24,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
- "revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb",
- "version" : "1.0.0"
+ "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53",
+ "version" : "1.0.5"
}
},
{
@@ -39,7 +40,7 @@
{
"identity" : "swift-composable-architecture",
"kind" : "remoteSourceControl",
- "location" : "git@github.com:pointfreeco/swift-composable-architecture.git",
+ "location" : "https://github.com/pointfreeco/swift-composable-architecture",
"state" : {
"revision" : "a7c1f799b55ecb418f85094b142565834f7ee7c7",
"version" : "1.2.0"
@@ -50,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
- "revision" : "ea631ce892687f5432a833312292b80db238186a",
- "version" : "1.0.0"
+ "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f",
+ "version" : "1.3.0"
}
},
{
@@ -68,8 +69,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
- "revision" : "4e1eb6e28afe723286d8cc60611237ffbddba7c5",
- "version" : "1.0.0"
+ "revision" : "e2b06090b0b7738fcd8c762131e705d711fc7e8e",
+ "version" : "1.6.0"
}
},
{
@@ -81,6 +82,15 @@
"version" : "1.0.0"
}
},
+ {
+ "identity" : "swift-syntax",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/swiftlang/swift-syntax",
+ "state" : {
+ "revision" : "0687f71944021d616d34d922343dcef086855920",
+ "version" : "600.0.1"
+ }
+ },
{
"identity" : "swiftui-navigation",
"kind" : "remoteSourceControl",
@@ -95,10 +105,10 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
- "revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
- "version" : "1.0.2"
+ "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1",
+ "version" : "1.4.3"
}
}
],
- "version" : 2
+ "version" : 3
}
diff --git a/Examples/LocationManager/Mobile/LocationManagerView.swift b/Examples/LocationManager/Mobile/LocationManagerView.swift
index 277bd96..d5b44eb 100644
--- a/Examples/LocationManager/Mobile/LocationManagerView.swift
+++ b/Examples/LocationManager/Mobile/LocationManagerView.swift
@@ -1,6 +1,6 @@
import Combine
import ComposableArchitecture
-import ComposableCoreLocation
+import CoreLocationClient
import MapKit
import SwiftUI
@@ -119,16 +119,16 @@ extension LocationManager {
static func mock() -> Self {
actor MockStore {
- let locationManagerSubject: CurrentValueSubject
+ let locationManagerSubject: LockIsolated>
var currentAuthorizationStatus: CLAuthorizationStatus {
didSet {
- locationManagerSubject.send(.didChangeAuthorization(currentAuthorizationStatus))
+ locationManagerSubject.value.send(.didChangeAuthorization(currentAuthorizationStatus))
}
}
- var currentLocation: ComposableCoreLocation.Location? {
+ var currentLocation: CoreLocationClient.Location? {
didSet {
- locationManagerSubject.send(
+ locationManagerSubject.value.send(
.didUpdateLocations(currentLocation.map { [$0] } ?? [])
)
}
@@ -136,14 +136,14 @@ extension LocationManager {
init(authorization: CLAuthorizationStatus) {
self.currentAuthorizationStatus = authorization
- self.locationManagerSubject = .init(.didChangeAuthorization(currentAuthorizationStatus))
+ self.locationManagerSubject = .init(.init(.didChangeAuthorization(authorization)))
}
func update(authorization: CLAuthorizationStatus) {
self.currentAuthorizationStatus = authorization
}
- func update(location: ComposableCoreLocation.Location) {
+ func update(location: CoreLocationClient.Location) {
self.currentLocation = location
}
}
@@ -159,7 +159,7 @@ extension LocationManager {
manager.delegate = {
AsyncStream { continuation in
- let cancellable = store.locationManagerSubject.sink { action in
+ let cancellable = store.locationManagerSubject.value.sink { action in
continuation.yield(action)
}
continuation.onTermination = { _ in
diff --git a/Package.resolved b/Package.resolved
index e2cfef1..0027a6c 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -1,12 +1,13 @@
{
+ "originHash" : "d8b7a276923e825c0118b9f2208446ff870e01ff3b4c61026967140f3677f7e8",
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
- "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb",
- "version" : "1.0.0"
+ "revision" : "9fa31f4403da54855f1e2aeaeff478f4f0e40b13",
+ "version" : "1.0.2"
}
},
{
@@ -14,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
- "revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb",
- "version" : "1.0.0"
+ "revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53",
+ "version" : "1.0.5"
}
},
{
@@ -23,8 +24,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
- "revision" : "ea631ce892687f5432a833312292b80db238186a",
- "version" : "1.0.0"
+ "revision" : "163409ef7dae9d960b87f34b51587b6609a76c1f",
+ "version" : "1.3.0"
}
},
{
@@ -32,8 +33,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
- "revision" : "4e1eb6e28afe723286d8cc60611237ffbddba7c5",
- "version" : "1.0.0"
+ "revision" : "e2b06090b0b7738fcd8c762131e705d711fc7e8e",
+ "version" : "1.6.0"
+ }
+ },
+ {
+ "identity" : "swift-syntax",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/swiftlang/swift-syntax",
+ "state" : {
+ "revision" : "0687f71944021d616d34d922343dcef086855920",
+ "version" : "600.0.1"
}
},
{
@@ -41,10 +51,10 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
- "revision" : "302891700c7fa3b92ebde9fe7b42933f8349f3c7",
- "version" : "1.0.0"
+ "revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1",
+ "version" : "1.4.3"
}
}
],
- "version" : 2
+ "version" : 3
}
diff --git a/Package.swift b/Package.swift
index 2a282de..04242c3 100644
--- a/Package.swift
+++ b/Package.swift
@@ -3,7 +3,7 @@
import PackageDescription
let package = Package(
- name: "composable-core-location",
+ name: "core-location-client",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
@@ -12,35 +12,36 @@ let package = Package(
],
products: [
.library(
- name: "ComposableCoreLocation",
- targets: ["ComposableCoreLocation"]
+ name: "CoreLocationClient",
+ targets: ["CoreLocationClient"]
)
],
dependencies: [
- .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
- .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.0.0"),
+ .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.6.0"),
+ .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.3.0"),
],
targets: [
.target(
- name: "ComposableCoreLocation",
+ name: "CoreLocationClient",
dependencies: [
.product(name: "Dependencies", package: "swift-dependencies"),
+ .product(name: "DependenciesMacros", package: "swift-dependencies"),
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
]
),
.testTarget(
- name: "ComposableCoreLocationTests",
- dependencies: ["ComposableCoreLocation"]
+ name: "CoreLocationClientTests",
+ dependencies: ["CoreLocationClient"]
),
]
)
-//for target in package.targets {
-// target.swiftSettings = target.swiftSettings ?? []
-// target.swiftSettings?.append(
-// .unsafeFlags([
-// "-Xfrontend", "-warn-concurrency",
-// "-Xfrontend", "-enable-actor-data-race-checks",
-// ])
-// )
-//}
+for target in package.targets {
+ target.swiftSettings = target.swiftSettings ?? []
+ target.swiftSettings?.append(
+ .unsafeFlags([
+ "-Xfrontend", "-warn-concurrency",
+ "-Xfrontend", "-enable-actor-data-race-checks",
+ ])
+ )
+}
diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift
new file mode 100644
index 0000000..d2db212
--- /dev/null
+++ b/Package@swift-6.0.swift
@@ -0,0 +1,47 @@
+// swift-tools-version:6.0
+
+import PackageDescription
+
+let package = Package(
+ name: "core-location-client",
+ platforms: [
+ .iOS(.v13),
+ .macOS(.v10_15),
+ .tvOS(.v13),
+ .watchOS(.v6),
+ ],
+ products: [
+ .library(
+ name: "CoreLocationClient",
+ targets: ["CoreLocationClient"]
+ )
+ ],
+ dependencies: [
+ .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.6.0"),
+ .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.3.0"),
+ ],
+ targets: [
+ .target(
+ name: "CoreLocationClient",
+ dependencies: [
+ .product(name: "Dependencies", package: "swift-dependencies"),
+ .product(name: "DependenciesMacros", package: "swift-dependencies"),
+ .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
+ ]
+ ),
+ .testTarget(
+ name: "CoreLocationClientTests",
+ dependencies: ["CoreLocationClient"]
+ ),
+ ]
+)
+
+for target in package.targets {
+ target.swiftSettings = target.swiftSettings ?? []
+ target.swiftSettings?.append(
+ .unsafeFlags([
+ "-Xfrontend", "-warn-concurrency",
+ "-Xfrontend", "-enable-actor-data-race-checks",
+ ])
+ )
+}
diff --git a/README.md b/README.md
index 4c79e59..bf852ea 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Check out the [LocationManager](./Examples/LocationManager) demo to see Composab
To use ComposableCoreLocation in your application, you can add an action to your domain that represents all of the actions the manager can emit via the `CLLocationManagerDelegate` methods:
```swift
-import ComposableCoreLocation
+import CoreLocationClient
enum AppAction {
case locationManager(LocationManager.Action)
diff --git a/Sources/ComposableCoreLocation/Failing.swift b/Sources/ComposableCoreLocation/Failing.swift
deleted file mode 100644
index 6c875ef..0000000
--- a/Sources/ComposableCoreLocation/Failing.swift
+++ /dev/null
@@ -1,74 +0,0 @@
-import CoreLocation
-import XCTestDynamicOverlay
-
-extension LocationManager {
- /// The failing implementation of the ``LocationManager`` interface. By default this
- /// implementation stubs all of its endpoints as functions that immediately call `XCTFail`.
- ///
- /// This allows you to test an even deeper property of your features: that they use only the
- /// location manager endpoints that you specify and nothing else. This can be useful as a
- /// measurement of just how complex a particular test is. Tests that need to stub many endpoints
- /// are in some sense more complicated than tests that only need to stub a few endpoints. It's not
- /// necessarily a bad thing to stub many endpoints. Sometimes it's needed.
- ///
- /// As an example, to create a failing manager that simulates a location manager that has already
- /// authorized access to location, and when a location is requested it immediately responds
- /// with a mock location we can do something like this:
- ///
- /// ```swift
- /// // Send actions to this subject to simulate the location manager's delegate methods
- /// // being called.
- /// let locationManagerSubject = PassthroughSubject()
- ///
- /// // The mock location we want the manager to say we are located at
- /// let mockLocation = Location(
- /// coordinate: CLLocationCoordinate2D(latitude: 40.6501, longitude: -73.94958),
- /// // A whole bunch of other properties have been omitted.
- /// )
- ///
- /// var manager = LocationManager.failing
- ///
- /// // Override any CLLocationManager endpoints your test invokes:
- /// manager.authorizationStatus = { .authorizedAlways }
- /// manager.delegate = { locationManagerSubject.eraseToEffect() }
- /// manager.locationServicesEnabled = { true }
- /// manager.requestLocation = {
- /// .fireAndForget { locationManagerSubject.send(.didUpdateLocations([mockLocation])) }
- /// }
- /// ```
- public static let failing = Self(
- accuracyAuthorization: XCTUnimplemented("\(Self.self).accuracyAuthorization"),
- authorizationStatus: XCTUnimplemented("\(Self.self).authorizationStatus"),
- delegate: XCTUnimplemented("\(Self.self).delegate"),
- dismissHeadingCalibrationDisplay: XCTUnimplemented(
- "\(Self.self).dismissHeadingCalibrationDisplay"),
- heading: XCTUnimplemented("\(Self.self).heading"),
- headingAvailable: XCTUnimplemented("\(Self.self).headingAvailable"),
- isRangingAvailable: XCTUnimplemented("\(Self.self).isRangingAvailable"),
- location: XCTUnimplemented("\(Self.self).location"),
- locationServicesEnabled: XCTUnimplemented("\(Self.self).locationServicesEnabled"),
- maximumRegionMonitoringDistance: XCTUnimplemented(
- "\(Self.self).maximumRegionMonitoringDistance"),
- monitoredRegions: XCTUnimplemented("\(Self.self).monitoredRegions"),
- requestAlwaysAuthorization: XCTUnimplemented("\(Self.self).requestAlwaysAuthorization"),
- requestLocation: XCTUnimplemented("\(Self.self).requestLocation"),
- requestWhenInUseAuthorization: XCTUnimplemented("\(Self.self).requestWhenInUseAuthorization"),
- requestTemporaryFullAccuracyAuthorization: XCTUnimplemented(
- "\(Self.self).requestTemporaryFullAccuracyAuthorization"),
- set: XCTUnimplemented("\(Self.self).set"),
- significantLocationChangeMonitoringAvailable: XCTUnimplemented(
- "\(Self.self).significantLocationChangeMonitoringAvailable"),
- startMonitoringForRegion: XCTUnimplemented("\(Self.self).startMonitoringForRegion"),
- startMonitoringSignificantLocationChanges: XCTUnimplemented(
- "\(Self.self).startMonitoringSignificantLocationChanges"),
- startMonitoringVisits: XCTUnimplemented("\(Self.self).startMonitoringVisits"),
- startUpdatingHeading: XCTUnimplemented("\(Self.self).startUpdatingHeading"),
- startUpdatingLocation: XCTUnimplemented("\(Self.self).startUpdatingLocation"),
- stopMonitoringForRegion: XCTUnimplemented("\(Self.self).stopMonitoringForRegion"),
- stopMonitoringSignificantLocationChanges: XCTUnimplemented(
- "\(Self.self).stopMonitoringSignificantLocationChanges"),
- stopMonitoringVisits: XCTUnimplemented("\(Self.self).stopMonitoringVisits"),
- stopUpdatingHeading: XCTUnimplemented("\(Self.self).stopUpdatingHeading"),
- stopUpdatingLocation: XCTUnimplemented("\(Self.self).stopUpdatingLocation")
- )
-}
diff --git a/Sources/ComposableCoreLocation/Interface.swift b/Sources/ComposableCoreLocation/Interface.swift
deleted file mode 100644
index acc81ed..0000000
--- a/Sources/ComposableCoreLocation/Interface.swift
+++ /dev/null
@@ -1,437 +0,0 @@
-import Combine
-import CoreLocation
-
-/// A wrapper around Core Location's `CLLocationManager` that exposes its functionality through
-/// effects and actions, making it easy to use with the Composable Architecture and easy to test.
-///
-/// To use it, one begins by adding an action to your domain that represents all of the actions the
-/// manager can emit via the `CLLocationManagerDelegate` methods:
-///
-/// ```swift
-/// import ComposableCoreLocation
-///
-/// enum AppAction {
-/// case locationManager(LocationManager.Action)
-///
-/// // Your domain's other actions:
-/// ...
-/// }
-/// ```
-///
-/// The `LocationManager.Action` enum holds a case for each delegate method of
-/// `CLLocationManagerDelegate`, such as `didUpdateLocations`, `didEnterRegion`, `didUpdateHeading`,
-/// and more.
-///
-/// Next we add a `LocationManager`, which is a wrapper around `CLLocationManager` that the library
-/// provides, to the application's environment of dependencies:
-///
-/// ```swift
-/// struct AppEnvironment {
-/// var locationManager: LocationManager
-///
-/// // Your domain's other dependencies:
-/// ...
-/// }
-/// ```
-///
-/// Then, we simultaneously subscribe to delegate actions and request authorization from our
-/// application's reducer by returning an effect from an action to kick things off. One good choice
-/// for such an action is the `onAppear` of your view.
-///
-/// ```swift
-/// let appReducer = Reducer {
-/// state, action, environment in
-///
-/// switch action {
-/// case .onAppear:
-/// return .merge(
-/// environment.locationManager
-/// .delegate()
-/// .map(AppAction.locationManager),
-///
-/// environment.locationManager
-/// .requestWhenInUseAuthorization()
-/// .fireAndForget()
-/// )
-///
-/// ...
-/// }
-/// }
-/// ```
-///
-/// With that initial setup we will now get all of `CLLocationManagerDelegate`'s delegate methods
-/// delivered to our reducer via actions. To handle a particular delegate action we can destructure
-/// it inside the `.locationManager` case we added to our `AppAction`. For example, once we get
-/// location authorization from the user we could request their current location:
-///
-/// ```swift
-/// case .locationManager(.didChangeAuthorization(.authorizedAlways)),
-/// .locationManager(.didChangeAuthorization(.authorizedWhenInUse)):
-///
-/// return environment.locationManager
-/// .requestLocation()
-/// .fireAndForget()
-/// ```
-///
-/// If the user denies location access we can show an alert telling them that we need access to be
-/// able to do anything in the app:
-///
-/// ```swift
-/// case .locationManager(.didChangeAuthorization(.denied)),
-/// .locationManager(.didChangeAuthorization(.restricted)):
-///
-/// state.alert = """
-/// Please give location access so that we can show you some cool stuff.
-/// """
-/// return .none
-/// ```
-///
-/// Otherwise, we'll be notified of the user's location by handling the `.didUpdateLocations`
-/// action:
-///
-/// ```swift
-/// case let .locationManager(.didUpdateLocations(locations)):
-/// // Do something cool with user's current location.
-/// ...
-/// ```
-///
-/// Once you have handled all the `CLLocationManagerDelegate` actions you care about, you can ignore
-/// the rest:
-///
-/// ```swift
-/// case .locationManager:
-/// return .none
-/// ```
-///
-/// And finally, when creating the `Store` to power your application you will supply the "live"
-/// implementation of the `LocationManager`, which is an instance that holds onto a
-/// `CLLocationManager` on the inside and interacts with it directly:
-///
-/// ```swift
-/// let store = Store(
-/// initialState: AppState(),
-/// reducer: appReducer,
-/// environment: AppEnvironment(
-/// locationManager: .live,
-/// // And your other dependencies...
-/// )
-/// )
-/// ```
-///
-/// This is enough to implement a basic application that interacts with Core Location.
-///
-/// The true power of building your application and interfacing with Core Location in this way is
-/// the ability to _test_ how your application interacts with Core Location. It starts by creating
-/// a `TestStore` whose environment contains a ``failing`` version of the `LocationManager`. Then,
-/// you can selectively override whichever endpoints your feature needs to supply deterministic
-/// functionality.
-///
-/// For example, to test the flow of asking for location authorization, being denied, and showing an
-/// alert, we need to override the `create` and `requestWhenInUseAuthorization` endpoints. The
-/// `create` endpoint needs to return an effect that emits the delegate actions, which we can
-/// control via a publish subject. And the `requestWhenInUseAuthorization` endpoint is a
-/// fire-and-forget effect, but we can make assertions that it was called how we expect.
-///
-/// ```swift
-/// let store = TestStore(
-/// initialState: AppState(),
-/// reducer: appReducer,
-/// environment: AppEnvironment(
-/// locationManager: .failing
-/// )
-/// )
-///
-/// var didRequestInUseAuthorization = false
-/// let locationManagerSubject = PassthroughSubject()
-///
-/// store.environment.locationManager.create = { locationManagerSubject.eraseToEffect() }
-/// store.environment.locationManager.requestWhenInUseAuthorization = {
-/// .fireAndForget { didRequestInUseAuthorization = true }
-/// }
-/// ```
-///
-/// Then we can write an assertion that simulates a sequence of user steps and location manager
-/// delegate actions, and we can assert against how state mutates and how effects are received. For
-/// example, we can have the user come to the screen, deny the location authorization request, and
-/// then assert that an effect was received which caused the alert to show:
-///
-/// ```swift
-/// store.send(.onAppear)
-///
-/// // Simulate the user denying location access
-/// locationManagerSubject.send(.didChangeAuthorization(.denied))
-///
-/// // We receive the authorization change delegate action from the effect
-/// store.receive(.locationManager(.didChangeAuthorization(.denied))) {
-/// $0.alert = """
-/// Please give location access so that we can show you some cool stuff.
-/// """
-///
-/// // Store assertions require all effects to be completed, so we complete
-/// // the subject manually.
-/// locationManagerSubject.send(completion: .finished)
-/// ```
-///
-/// And this is only the tip of the iceberg. We can further test what happens when we are granted
-/// authorization by the user and the request for their location returns a specific location that we
-/// control, and even what happens when the request for their location fails. It is very easy to
-/// write these tests, and we can test deep, subtle properties of our application.
-///
-public struct LocationManager: Sendable {
- /// Actions that correspond to `CLLocationManagerDelegate` methods.
- ///
- /// See `CLLocationManagerDelegate` for more information.
- public enum Action: Equatable {
- case didChangeAuthorization(CLAuthorizationStatus)
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didDetermineState(CLRegionState, region: Region)
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didEnterRegion(Region)
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didExitRegion(Region)
-
- @available(macOS, unavailable)
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didFailRanging(beaconConstraint: CLBeaconIdentityConstraint, error: Error)
-
- case didFailWithError(Error)
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didFinishDeferredUpdatesWithError(Error?)
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didPauseLocationUpdates
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didResumeLocationUpdates
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didStartMonitoring(region: Region)
-
- @available(macOS, unavailable)
- @available(tvOS, unavailable)
- case didUpdateHeading(newHeading: Heading)
-
- case didUpdateLocations([Location])
-
- @available(macCatalyst, deprecated: 13)
- @available(tvOS, unavailable)
- case didUpdateTo(newLocation: Location, oldLocation: Location)
-
- @available(macOS, unavailable)
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didVisit(Visit)
-
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case monitoringDidFail(region: Region?, error: Error)
-
- @available(macOS, unavailable)
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- case didRangeBeacons([Beacon], satisfyingConstraint: CLBeaconIdentityConstraint)
- }
-
- public struct Error: Swift.Error, Equatable {
- public let error: NSError
-
- public init(_ error: Swift.Error) {
- self.error = error as NSError
- }
- }
-
- public var accuracyAuthorization: @Sendable () async -> AccuracyAuthorization?
-
- public var authorizationStatus: @Sendable () async -> CLAuthorizationStatus
-
- public var delegate: @Sendable () async -> AsyncStream
-
- public var dismissHeadingCalibrationDisplay: @Sendable () async -> Void
-
- public var heading: @Sendable () async -> Heading?
-
- public var headingAvailable: @Sendable () async -> Bool
-
- public var isRangingAvailable: @Sendable () async -> Bool
-
- public var location: @Sendable () async -> Location?
-
- public var locationServicesEnabled: @Sendable () async -> Bool
-
- public var maximumRegionMonitoringDistance: @Sendable () async -> CLLocationDistance
-
- public var monitoredRegions: @Sendable () async -> Set
-
- public var requestAlwaysAuthorization: @Sendable () async -> Void
-
- public var requestLocation: @Sendable () async -> Void
-
- public var requestWhenInUseAuthorization: @Sendable () async -> Void
-
- public var requestTemporaryFullAccuracyAuthorization: @Sendable (String) async throws -> Void
-
- public var set: @Sendable (Properties) async -> Void
-
- public var significantLocationChangeMonitoringAvailable: @Sendable () async -> Bool
-
- public var startMonitoringForRegion: @Sendable (Region) async -> Void
-
- public var startMonitoringSignificantLocationChanges: @Sendable () async -> Void
-
- public var startMonitoringVisits: @Sendable () async -> Void
-
- public var startUpdatingHeading: @Sendable () async -> Void
-
- public var startUpdatingLocation: @Sendable () async -> Void
-
- public var stopMonitoringForRegion: @Sendable (Region) async -> Void
-
- public var stopMonitoringSignificantLocationChanges: @Sendable () async -> Void
-
- public var stopMonitoringVisits: @Sendable () async -> Void
-
- public var stopUpdatingHeading: @Sendable () async -> Void
-
- public var stopUpdatingLocation: @Sendable () async -> Void
-
- /// Updates the given properties of a uniquely identified `CLLocationManager`.
- @Sendable public func set(
- activityType: CLActivityType? = nil,
- allowsBackgroundLocationUpdates: Bool? = nil,
- desiredAccuracy: CLLocationAccuracy? = nil,
- distanceFilter: CLLocationDistance? = nil,
- headingFilter: CLLocationDegrees? = nil,
- headingOrientation: CLDeviceOrientation? = nil,
- pausesLocationUpdatesAutomatically: Bool? = nil,
- showsBackgroundLocationIndicator: Bool? = nil
- ) async {
- #if os(macOS) || os(tvOS) || os(watchOS)
- #else
- await self.set(
- Properties(
- activityType: activityType,
- allowsBackgroundLocationUpdates: allowsBackgroundLocationUpdates,
- desiredAccuracy: desiredAccuracy,
- distanceFilter: distanceFilter,
- headingFilter: headingFilter,
- headingOrientation: headingOrientation,
- pausesLocationUpdatesAutomatically: pausesLocationUpdatesAutomatically,
- showsBackgroundLocationIndicator: showsBackgroundLocationIndicator
- )
- )
- #endif
- }
-}
-
-extension LocationManager {
- public struct Properties: Equatable {
- var activityType: CLActivityType? = nil
-
- var allowsBackgroundLocationUpdates: Bool? = nil
-
- var desiredAccuracy: CLLocationAccuracy? = nil
-
- var distanceFilter: CLLocationDistance? = nil
-
- var headingFilter: CLLocationDegrees? = nil
-
- var headingOrientation: CLDeviceOrientation? = nil
-
- var pausesLocationUpdatesAutomatically: Bool? = nil
-
- var showsBackgroundLocationIndicator: Bool? = nil
-
- public static func == (lhs: Self, rhs: Self) -> Bool {
- var isEqual = true
-#if os(iOS) || targetEnvironment(macCatalyst) || os(watchOS)
- isEqual =
- isEqual
- && lhs.activityType == rhs.activityType
- && lhs.allowsBackgroundLocationUpdates == rhs.allowsBackgroundLocationUpdates
-#endif
- isEqual =
- isEqual
- && lhs.desiredAccuracy == rhs.desiredAccuracy
- && lhs.distanceFilter == rhs.distanceFilter
-#if os(iOS) || targetEnvironment(macCatalyst) || os(watchOS)
- isEqual =
- isEqual
- && lhs.headingFilter == rhs.headingFilter
- && lhs.headingOrientation == rhs.headingOrientation
-#endif
-#if os(iOS) || targetEnvironment(macCatalyst)
- isEqual =
- isEqual
- && lhs.pausesLocationUpdatesAutomatically == rhs.pausesLocationUpdatesAutomatically
- && lhs.showsBackgroundLocationIndicator == rhs.showsBackgroundLocationIndicator
-#endif
- return isEqual
- }
-
- @available(macOS, unavailable)
- @available(tvOS, unavailable)
- @available(watchOS, unavailable)
- public init(
- activityType: CLActivityType? = nil,
- allowsBackgroundLocationUpdates: Bool? = nil,
- desiredAccuracy: CLLocationAccuracy? = nil,
- distanceFilter: CLLocationDistance? = nil,
- headingFilter: CLLocationDegrees? = nil,
- headingOrientation: CLDeviceOrientation? = nil,
- pausesLocationUpdatesAutomatically: Bool? = nil,
- showsBackgroundLocationIndicator: Bool? = nil
- ) {
- self.activityType = activityType
- self.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates
- self.desiredAccuracy = desiredAccuracy
- self.distanceFilter = distanceFilter
- self.headingFilter = headingFilter
- self.headingOrientation = headingOrientation
- self.pausesLocationUpdatesAutomatically = pausesLocationUpdatesAutomatically
- self.showsBackgroundLocationIndicator = showsBackgroundLocationIndicator
- }
-
- @available(iOS, unavailable)
- @available(macCatalyst, unavailable)
- @available(watchOS, unavailable)
- public init(
- desiredAccuracy: CLLocationAccuracy? = nil,
- distanceFilter: CLLocationDistance? = nil
- ) {
- self.desiredAccuracy = desiredAccuracy
- self.distanceFilter = distanceFilter
- }
-
- @available(iOS, unavailable)
- @available(macCatalyst, unavailable)
- @available(macOS, unavailable)
- @available(tvOS, unavailable)
- public init(
- activityType: CLActivityType? = nil,
- allowsBackgroundLocationUpdates: Bool? = nil,
- desiredAccuracy: CLLocationAccuracy? = nil,
- distanceFilter: CLLocationDistance? = nil,
- headingFilter: CLLocationDegrees? = nil,
- headingOrientation: CLDeviceOrientation? = nil
- ) {
- self.activityType = activityType
- self.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates
- self.desiredAccuracy = desiredAccuracy
- self.distanceFilter = distanceFilter
- self.headingFilter = headingFilter
- self.headingOrientation = headingOrientation
- }
- }
-}
diff --git a/Sources/ComposableCoreLocation/Dependencies.swift b/Sources/CoreLocationClient/Dependencies.swift
similarity index 73%
rename from Sources/ComposableCoreLocation/Dependencies.swift
rename to Sources/CoreLocationClient/Dependencies.swift
index 5f0ffa3..5e49380 100644
--- a/Sources/ComposableCoreLocation/Dependencies.swift
+++ b/Sources/CoreLocationClient/Dependencies.swift
@@ -8,6 +8,6 @@ extension DependencyValues {
}
extension LocationManager: DependencyKey {
- public static let testValue = Self.failing
- public static var liveValue = Self.live
+ public static let testValue = Self()
+ public static let liveValue = Self.live
}
diff --git a/Sources/CoreLocationClient/Interface.swift b/Sources/CoreLocationClient/Interface.swift
new file mode 100644
index 0000000..c463c0e
--- /dev/null
+++ b/Sources/CoreLocationClient/Interface.swift
@@ -0,0 +1,439 @@
+import CoreLocation
+import Dependencies
+import DependenciesMacros
+
+/// A wrapper around Core Location's `CLLocationManager` that exposes its functionality through
+/// effects and actions, making it easy to use with the Composable Architecture and easy to test.
+///
+/// To use it, one begins by adding an action to your domain that represents all of the actions the
+/// manager can emit via the `CLLocationManagerDelegate` methods:
+///
+/// ```swift
+/// import CoreLocationClient
+///
+/// enum AppAction {
+/// case locationManager(LocationManager.Action)
+///
+/// // Your domain's other actions:
+/// ...
+/// }
+/// ```
+///
+/// The `LocationManager.Action` enum holds a case for each delegate method of
+/// `CLLocationManagerDelegate`, such as `didUpdateLocations`, `didEnterRegion`, `didUpdateHeading`,
+/// and more.
+///
+/// Next we add a `LocationManager`, which is a wrapper around `CLLocationManager` that the library
+/// provides, to the application's environment of dependencies:
+///
+/// ```swift
+/// struct AppEnvironment {
+/// var locationManager: LocationManager
+///
+/// // Your domain's other dependencies:
+/// ...
+/// }
+/// ```
+///
+/// Then, we simultaneously subscribe to delegate actions and request authorization from our
+/// application's reducer by returning an effect from an action to kick things off. One good choice
+/// for such an action is the `onAppear` of your view.
+///
+/// ```swift
+/// let appReducer = Reducer {
+/// state, action, environment in
+///
+/// switch action {
+/// case .onAppear:
+/// return .merge(
+/// environment.locationManager
+/// .delegate()
+/// .map(AppAction.locationManager),
+///
+/// environment.locationManager
+/// .requestWhenInUseAuthorization()
+/// .fireAndForget()
+/// )
+///
+/// ...
+/// }
+/// }
+/// ```
+///
+/// With that initial setup we will now get all of `CLLocationManagerDelegate`'s delegate methods
+/// delivered to our reducer via actions. To handle a particular delegate action we can destructure
+/// it inside the `.locationManager` case we added to our `AppAction`. For example, once we get
+/// location authorization from the user we could request their current location:
+///
+/// ```swift
+/// case .locationManager(.didChangeAuthorization(.authorizedAlways)),
+/// .locationManager(.didChangeAuthorization(.authorizedWhenInUse)):
+///
+/// return environment.locationManager
+/// .requestLocation()
+/// .fireAndForget()
+/// ```
+///
+/// If the user denies location access we can show an alert telling them that we need access to be
+/// able to do anything in the app:
+///
+/// ```swift
+/// case .locationManager(.didChangeAuthorization(.denied)),
+/// .locationManager(.didChangeAuthorization(.restricted)):
+///
+/// state.alert = """
+/// Please give location access so that we can show you some cool stuff.
+/// """
+/// return .none
+/// ```
+///
+/// Otherwise, we'll be notified of the user's location by handling the `.didUpdateLocations`
+/// action:
+///
+/// ```swift
+/// case let .locationManager(.didUpdateLocations(locations)):
+/// // Do something cool with user's current location.
+/// ...
+/// ```
+///
+/// Once you have handled all the `CLLocationManagerDelegate` actions you care about, you can ignore
+/// the rest:
+///
+/// ```swift
+/// case .locationManager:
+/// return .none
+/// ```
+///
+/// And finally, when creating the `Store` to power your application you will supply the "live"
+/// implementation of the `LocationManager`, which is an instance that holds onto a
+/// `CLLocationManager` on the inside and interacts with it directly:
+///
+/// ```swift
+/// let store = Store(
+/// initialState: AppState(),
+/// reducer: appReducer,
+/// environment: AppEnvironment(
+/// locationManager: .live,
+/// // And your other dependencies...
+/// )
+/// )
+/// ```
+///
+/// This is enough to implement a basic application that interacts with Core Location.
+///
+/// The true power of building your application and interfacing with Core Location in this way is
+/// the ability to _test_ how your application interacts with Core Location. It starts by creating
+/// a `TestStore` whose environment contains a ``failing`` version of the `LocationManager`. Then,
+/// you can selectively override whichever endpoints your feature needs to supply deterministic
+/// functionality.
+///
+/// For example, to test the flow of asking for location authorization, being denied, and showing an
+/// alert, we need to override the `create` and `requestWhenInUseAuthorization` endpoints. The
+/// `create` endpoint needs to return an effect that emits the delegate actions, which we can
+/// control via a publish subject. And the `requestWhenInUseAuthorization` endpoint is a
+/// fire-and-forget effect, but we can make assertions that it was called how we expect.
+///
+/// ```swift
+/// let store = TestStore(
+/// initialState: AppState(),
+/// reducer: appReducer,
+/// environment: AppEnvironment(
+/// locationManager: .failing
+/// )
+/// )
+///
+/// var didRequestInUseAuthorization = false
+/// let locationManagerSubject = PassthroughSubject()
+///
+/// store.environment.locationManager.create = { locationManagerSubject.eraseToEffect() }
+/// store.environment.locationManager.requestWhenInUseAuthorization = {
+/// .fireAndForget { didRequestInUseAuthorization = true }
+/// }
+/// ```
+///
+/// Then we can write an assertion that simulates a sequence of user steps and location manager
+/// delegate actions, and we can assert against how state mutates and how effects are received. For
+/// example, we can have the user come to the screen, deny the location authorization request, and
+/// then assert that an effect was received which caused the alert to show:
+///
+/// ```swift
+/// store.send(.onAppear)
+///
+/// // Simulate the user denying location access
+/// locationManagerSubject.send(.didChangeAuthorization(.denied))
+///
+/// // We receive the authorization change delegate action from the effect
+/// store.receive(.locationManager(.didChangeAuthorization(.denied))) {
+/// $0.alert = """
+/// Please give location access so that we can show you some cool stuff.
+/// """
+///
+/// // Store assertions require all effects to be completed, so we complete
+/// // the subject manually.
+/// locationManagerSubject.send(completion: .finished)
+/// ```
+///
+/// And this is only the tip of the iceberg. We can further test what happens when we are granted
+/// authorization by the user and the request for their location returns a specific location that we
+/// control, and even what happens when the request for their location fails. It is very easy to
+/// write these tests, and we can test deep, subtle properties of our application.
+///
+@DependencyClient
+public struct LocationManager: Sendable {
+ /// Actions that correspond to `CLLocationManagerDelegate` methods.
+ ///
+ /// See `CLLocationManagerDelegate` for more information.
+ public enum Action: Equatable, Sendable {
+ case didChangeAuthorization(CLAuthorizationStatus)
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didDetermineState(CLRegionState, region: Region)
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didEnterRegion(Region)
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didExitRegion(Region)
+
+ @available(macOS, unavailable)
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didFailRanging(beaconConstraint: CLBeaconIdentityConstraint, error: Error)
+
+ case didFailWithError(Error)
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didFinishDeferredUpdatesWithError(Error?)
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didPauseLocationUpdates
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didResumeLocationUpdates
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didStartMonitoring(region: Region)
+
+ @available(macOS, unavailable)
+ @available(tvOS, unavailable)
+ case didUpdateHeading(newHeading: Heading)
+
+ case didUpdateLocations([Location])
+
+ @available(macCatalyst, deprecated: 13)
+ @available(tvOS, unavailable)
+ case didUpdateTo(newLocation: Location, oldLocation: Location)
+
+ @available(macOS, unavailable)
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didVisit(Visit)
+
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case monitoringDidFail(region: Region?, error: Error)
+
+ @available(macOS, unavailable)
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ case didRangeBeacons([Beacon], satisfyingConstraint: CLBeaconIdentityConstraint)
+ }
+
+ public struct Error: Swift.Error, Equatable {
+ public let error: NSError
+
+ public init(_ error: Swift.Error) {
+ self.error = error as NSError
+ }
+ }
+
+ public var accuracyAuthorization: @Sendable () async -> AccuracyAuthorization?
+
+ public var authorizationStatus: @Sendable () async -> CLAuthorizationStatus = { .notDetermined }
+
+ public var delegate: @Sendable () async -> AsyncStream = { .never }
+
+ public var dismissHeadingCalibrationDisplay: @Sendable () async -> Void
+
+ public var heading: @Sendable () async -> Heading?
+
+ public var headingAvailable: @Sendable () async -> Bool = { false }
+
+ public var isRangingAvailable: @Sendable () async -> Bool = { false }
+
+ public var location: @Sendable () async -> Location?
+
+ public var locationServicesEnabled: @Sendable () async -> Bool = { false }
+
+ public var maximumRegionMonitoringDistance: @Sendable () async -> CLLocationDistance = { .nan }
+
+ public var monitoredRegions: @Sendable () async -> Set = { .init() }
+
+ public var requestAlwaysAuthorization: @Sendable () async -> Void
+
+ public var requestLocation: @Sendable () async -> Void
+
+ public var requestWhenInUseAuthorization: @Sendable () async -> Void
+
+ public var requestTemporaryFullAccuracyAuthorization: @Sendable (String) async throws -> Void
+
+ public var set: @Sendable (Properties) async -> Void
+
+ public var significantLocationChangeMonitoringAvailable: @Sendable () async -> Bool = { false }
+
+ public var startMonitoringForRegion: @Sendable (Region) async -> Void
+
+ public var startMonitoringSignificantLocationChanges: @Sendable () async -> Void
+
+ public var startMonitoringVisits: @Sendable () async -> Void
+
+ public var startUpdatingHeading: @Sendable () async -> Void
+
+ public var startUpdatingLocation: @Sendable () async -> Void
+
+ public var stopMonitoringForRegion: @Sendable (Region) async -> Void
+
+ public var stopMonitoringSignificantLocationChanges: @Sendable () async -> Void
+
+ public var stopMonitoringVisits: @Sendable () async -> Void
+
+ public var stopUpdatingHeading: @Sendable () async -> Void
+
+ public var stopUpdatingLocation: @Sendable () async -> Void
+
+ /// Updates the given properties of a uniquely identified `CLLocationManager`.
+ @Sendable public func set(
+ activityType: CLActivityType? = nil,
+ allowsBackgroundLocationUpdates: Bool? = nil,
+ desiredAccuracy: CLLocationAccuracy? = nil,
+ distanceFilter: CLLocationDistance? = nil,
+ headingFilter: CLLocationDegrees? = nil,
+ headingOrientation: CLDeviceOrientation? = nil,
+ pausesLocationUpdatesAutomatically: Bool? = nil,
+ showsBackgroundLocationIndicator: Bool? = nil
+ ) async {
+#if os(macOS) || os(tvOS) || os(watchOS)
+#else
+ await self.set(
+ Properties(
+ activityType: activityType,
+ allowsBackgroundLocationUpdates: allowsBackgroundLocationUpdates,
+ desiredAccuracy: desiredAccuracy,
+ distanceFilter: distanceFilter,
+ headingFilter: headingFilter,
+ headingOrientation: headingOrientation,
+ pausesLocationUpdatesAutomatically: pausesLocationUpdatesAutomatically,
+ showsBackgroundLocationIndicator: showsBackgroundLocationIndicator
+ )
+ )
+#endif
+ }
+}
+
+extension LocationManager {
+ public struct Properties: Equatable {
+ var activityType: CLActivityType? = nil
+
+ var allowsBackgroundLocationUpdates: Bool? = nil
+
+ var desiredAccuracy: CLLocationAccuracy? = nil
+
+ var distanceFilter: CLLocationDistance? = nil
+
+ var headingFilter: CLLocationDegrees? = nil
+
+ var headingOrientation: CLDeviceOrientation? = nil
+
+ var pausesLocationUpdatesAutomatically: Bool? = nil
+
+ var showsBackgroundLocationIndicator: Bool? = nil
+
+ public static func == (lhs: Self, rhs: Self) -> Bool {
+ var isEqual = true
+#if os(iOS) || targetEnvironment(macCatalyst) || os(watchOS)
+ isEqual =
+ isEqual
+ && lhs.activityType == rhs.activityType
+ && lhs.allowsBackgroundLocationUpdates == rhs.allowsBackgroundLocationUpdates
+#endif
+ isEqual =
+ isEqual
+ && lhs.desiredAccuracy == rhs.desiredAccuracy
+ && lhs.distanceFilter == rhs.distanceFilter
+#if os(iOS) || targetEnvironment(macCatalyst) || os(watchOS)
+ isEqual =
+ isEqual
+ && lhs.headingFilter == rhs.headingFilter
+ && lhs.headingOrientation == rhs.headingOrientation
+#endif
+#if os(iOS) || targetEnvironment(macCatalyst)
+ isEqual =
+ isEqual
+ && lhs.pausesLocationUpdatesAutomatically == rhs.pausesLocationUpdatesAutomatically
+ && lhs.showsBackgroundLocationIndicator == rhs.showsBackgroundLocationIndicator
+#endif
+ return isEqual
+ }
+
+ @available(macOS, unavailable)
+ @available(tvOS, unavailable)
+ @available(watchOS, unavailable)
+ public init(
+ activityType: CLActivityType? = nil,
+ allowsBackgroundLocationUpdates: Bool? = nil,
+ desiredAccuracy: CLLocationAccuracy? = nil,
+ distanceFilter: CLLocationDistance? = nil,
+ headingFilter: CLLocationDegrees? = nil,
+ headingOrientation: CLDeviceOrientation? = nil,
+ pausesLocationUpdatesAutomatically: Bool? = nil,
+ showsBackgroundLocationIndicator: Bool? = nil
+ ) {
+ self.activityType = activityType
+ self.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates
+ self.desiredAccuracy = desiredAccuracy
+ self.distanceFilter = distanceFilter
+ self.headingFilter = headingFilter
+ self.headingOrientation = headingOrientation
+ self.pausesLocationUpdatesAutomatically = pausesLocationUpdatesAutomatically
+ self.showsBackgroundLocationIndicator = showsBackgroundLocationIndicator
+ }
+
+ @available(iOS, unavailable)
+ @available(macCatalyst, unavailable)
+ @available(watchOS, unavailable)
+ public init(
+ desiredAccuracy: CLLocationAccuracy? = nil,
+ distanceFilter: CLLocationDistance? = nil
+ ) {
+ self.desiredAccuracy = desiredAccuracy
+ self.distanceFilter = distanceFilter
+ }
+
+ @available(iOS, unavailable)
+ @available(macCatalyst, unavailable)
+ @available(macOS, unavailable)
+ @available(tvOS, unavailable)
+ public init(
+ activityType: CLActivityType? = nil,
+ allowsBackgroundLocationUpdates: Bool? = nil,
+ desiredAccuracy: CLLocationAccuracy? = nil,
+ distanceFilter: CLLocationDistance? = nil,
+ headingFilter: CLLocationDegrees? = nil,
+ headingOrientation: CLDeviceOrientation? = nil
+ ) {
+ self.activityType = activityType
+ self.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates
+ self.desiredAccuracy = desiredAccuracy
+ self.distanceFilter = distanceFilter
+ self.headingFilter = headingFilter
+ self.headingOrientation = headingOrientation
+ }
+ }
+}
diff --git a/Sources/ComposableCoreLocation/Internal/Deprecations.swift b/Sources/CoreLocationClient/Internal/Deprecations.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Internal/Deprecations.swift
rename to Sources/CoreLocationClient/Internal/Deprecations.swift
diff --git a/Sources/ComposableCoreLocation/Internal/Exports.swift b/Sources/CoreLocationClient/Internal/Exports.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Internal/Exports.swift
rename to Sources/CoreLocationClient/Internal/Exports.swift
diff --git a/Sources/ComposableCoreLocation/Live.swift b/Sources/CoreLocationClient/Live.swift
similarity index 97%
rename from Sources/ComposableCoreLocation/Live.swift
rename to Sources/CoreLocationClient/Live.swift
index d69f234..f319817 100644
--- a/Sources/ComposableCoreLocation/Live.swift
+++ b/Sources/CoreLocationClient/Live.swift
@@ -212,7 +212,7 @@ private struct LocationManagerSendableBox: Sendable {
}
private final class LocationManagerDelegate: NSObject, CLLocationManagerDelegate, Sendable {
- let continuations: ActorIsolated<[UUID: AsyncStream.Continuation]>
+ let continuations: LockIsolated<[UUID: AsyncStream.Continuation]>
override init() {
self.continuations = .init([:])
@@ -221,7 +221,7 @@ private final class LocationManagerDelegate: NSObject, CLLocationManagerDelegate
func registerContinuation(_ continuation: AsyncStream.Continuation) {
Task { [continuations] in
- await continuations.withValue {
+ continuations.withValue {
let id = UUID()
$0[id] = continuation
continuation.onTermination = { [weak self] _ in self?.unregisterContinuation(withID: id) }
@@ -230,12 +230,12 @@ private final class LocationManagerDelegate: NSObject, CLLocationManagerDelegate
}
private func unregisterContinuation(withID id: UUID) {
- Task { [continuations] in await continuations.withValue { $0.removeValue(forKey: id) } }
+ Task { [continuations] in continuations.withValue { $0.removeValue(forKey: id) } }
}
private func send(_ action: LocationManager.Action) {
Task { [continuations] in
- await continuations.withValue { $0.values.forEach { $0.yield(action) } }
+ continuations.withValue { $0.values.forEach { $0.yield(action) } }
}
}
diff --git a/Sources/ComposableCoreLocation/Models/AccuracyAuthorization.swift b/Sources/CoreLocationClient/Models/AccuracyAuthorization.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Models/AccuracyAuthorization.swift
rename to Sources/CoreLocationClient/Models/AccuracyAuthorization.swift
diff --git a/Sources/ComposableCoreLocation/Models/Beacon.swift b/Sources/CoreLocationClient/Models/Beacon.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Models/Beacon.swift
rename to Sources/CoreLocationClient/Models/Beacon.swift
diff --git a/Sources/ComposableCoreLocation/Models/BeaconConstraint.swift b/Sources/CoreLocationClient/Models/BeaconConstraint.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Models/BeaconConstraint.swift
rename to Sources/CoreLocationClient/Models/BeaconConstraint.swift
diff --git a/Sources/ComposableCoreLocation/Models/Heading.swift b/Sources/CoreLocationClient/Models/Heading.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Models/Heading.swift
rename to Sources/CoreLocationClient/Models/Heading.swift
diff --git a/Sources/ComposableCoreLocation/Models/Location.swift b/Sources/CoreLocationClient/Models/Location.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Models/Location.swift
rename to Sources/CoreLocationClient/Models/Location.swift
diff --git a/Sources/ComposableCoreLocation/Models/Region.swift b/Sources/CoreLocationClient/Models/Region.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Models/Region.swift
rename to Sources/CoreLocationClient/Models/Region.swift
diff --git a/Sources/ComposableCoreLocation/Models/Visit.swift b/Sources/CoreLocationClient/Models/Visit.swift
similarity index 100%
rename from Sources/ComposableCoreLocation/Models/Visit.swift
rename to Sources/CoreLocationClient/Models/Visit.swift
diff --git a/Tests/ComposableCoreLocationTests/ComposableCoreLocationTests.swift b/Tests/CoreLocationClientTests/ComposableCoreLocationTests.swift
similarity index 99%
rename from Tests/ComposableCoreLocationTests/ComposableCoreLocationTests.swift
rename to Tests/CoreLocationClientTests/ComposableCoreLocationTests.swift
index 82dd44d..d8c2179 100644
--- a/Tests/ComposableCoreLocationTests/ComposableCoreLocationTests.swift
+++ b/Tests/CoreLocationClientTests/ComposableCoreLocationTests.swift
@@ -1,4 +1,4 @@
-import ComposableCoreLocation
+import CoreLocationClient
import XCTest
class ComposableCoreLocationTests: XCTestCase {