可组合的 Core Location

CI

Composable Core Location 是一个桥接了 Composable ArchitectureCore Location 的库。

示例

查看 LocationManager 示例,了解 ComposableCoreLocation 的实际应用。

基本用法

要在你的应用中使用 ComposableCoreLocation,你可以向你的域添加一个 action,用于表示 manager 可以通过 CLLocationManagerDelegate 方法发出的所有 action

import ComposableCoreLocation

enum AppAction {
  case locationManager(LocationManager.Action)

  // Your domain's other actions:
  ...
}

LocationManager.Action 枚举为 CLLocationManagerDelegate 的每个委托方法都持有一个 case,例如 didUpdateLocationsdidEnterRegiondidUpdateHeading 等。

接下来,我们将库提供的 CLLocationManager 包装器 LocationManager 添加到应用程序的依赖环境。

struct AppEnvironment {
  var locationManager: LocationManager

  // Your domain's other dependencies:
  ...
}

然后,我们通过从一个 action 返回一个 effect 来同时订阅委托 action 并从应用程序的 reducer 请求授权以启动操作。对于这样的 action,一个好的选择是你的 view 的 onAppear

let appReducer = Reducer<AppState, AppAction, AppEnvironment> {
  state, action, environment in

  switch action {
  case .onAppear:
    return .merge(
      environment.locationManager
        .delegate()
        .map(AppAction.locationManager),

      environment.locationManager
        .requestWhenInUseAuthorization()
        .fireAndForget()
    )

  ...
  }
}

通过初始设置,我们现在将通过 actions 将 CLLocationManagerDelegate 的所有委托方法传递到我们的 reducer。为了处理特定的委托 action,我们可以在添加到 AppAction.locationManager case 中解构它。例如,一旦我们从用户那里获得位置授权,我们就可以请求他们当前的位置

case .locationManager(.didChangeAuthorization(.authorizedAlways)),
     .locationManager(.didChangeAuthorization(.authorizedWhenInUse)):

  return environment.locationManager
    .requestLocation()
    .fireAndForget()

如果用户拒绝位置访问,我们可以显示一个警报,告诉他们我们需要访问权限才能在应用中执行任何操作

case .locationManager(.didChangeAuthorization(.denied)),
     .locationManager(.didChangeAuthorization(.restricted)):

  state.alert = """
    Please give location access so that we can show you some cool stuff.
    """
  return .none

否则,我们将通过处理 .didUpdateLocations action 来收到用户位置的通知

case let .locationManager(.didUpdateLocations(locations)):
  // Do something cool with user's current location.
  ...

一旦你处理完所有你关心的 CLLocationManagerDelegate actions,你可以忽略其余的

case .locationManager:
  return .none

最后,在创建用于驱动你的应用程序的 Store 时,你将提供 LocationManager 的“live”实现,这是一个在内部持有 CLLocationManager 并直接与之交互的实例

let store = Store(
  initialState: AppState(),
  reducer: appReducer,
  environment: AppEnvironment(
    locationManager: .live,
    // And your other dependencies...
  )
)

这足以实现一个与 Core Location 交互的基本应用程序。

以这种方式构建应用程序并与 Core Location 交互的真正强大之处在于能够 *测试* 你的应用程序如何与 Core Location 交互。它从创建一个 TestStore 开始,其环境包含 LocationManager.failing 版本。然后,你可以选择性地覆盖你的功能需要提供的确定性功能的任何端点。

例如,为了测试请求位置授权、被拒绝和显示警报的流程,我们需要覆盖 createrequestWhenInUseAuthorization 端点。create 端点需要返回一个 effect,该 effect 发出委托 action,我们可以通过发布 subject 来控制它。而 requestWhenInUseAuthorization 端点是一个 fire-and-forget effect,但我们可以断言它是否按我们期望的方式被调用。

let store = TestStore(
  initialState: AppState(),
  reducer: appReducer,
  environment: AppEnvironment(
    locationManager: .failing
  )
)

var didRequestInUseAuthorization = false
let locationManagerSubject = PassthroughSubject<LocationManager.Action, Never>()

store.environment.locationManager.create = { locationManagerSubject.eraseToEffect() }
store.environment.locationManager.requestWhenInUseAuthorization = {
  .fireAndForget { didRequestInUseAuthorization = true }
}

然后,我们可以编写一个断言,模拟用户步骤和位置管理器委托 action 的序列,并且我们可以断言状态如何变化以及如何接收 effect。例如,我们可以让用户来到屏幕,拒绝位置授权请求,然后断言收到了一个 effect,该 effect 导致警报显示

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)

而这仅仅是冰山一角。我们可以进一步测试当用户授予我们授权并且对他们位置的请求返回我们控制的特定位置时会发生什么,甚至当对他们位置的请求失败时会发生什么。编写这些测试非常容易,我们可以测试应用程序的深层、微妙的属性。

安装

你可以通过将其作为包依赖项添加到 Xcode 项目来添加 ComposableCoreLocation。

  1. File 菜单中,选择 Swift Packages › Add Package Dependency…
  2. 在包仓库 URL 文本字段中输入 “https://github.com/pointfreeco/composable-core-location

文档

Composable Core Location API 的最新文档可在此处获取

帮助

如果你想讨论 Composable Core Location 和 Composable Architecture,或者对如何使用它们来解决特定问题有疑问,请在其 Swift 论坛上提问。

许可证

此库在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE