Advanced-NSOperations/Earthquakes/Operations/LocationCondition.swift

144 lines
4.8 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Copyright (C) 2015 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
This file shows an example of implementing the OperationCondition protocol.
*/
import CoreLocation
/// A condition for verifying access to the user's location.
struct LocationCondition: OperationCondition {
/**
Declare a new enum instead of using `CLAuthorizationStatus`, because that
enum has more case values than are necessary for our purposes.
*/
enum Usage {
case WhenInUse
case Always
}
static let name = "Location"
static let locationServicesEnabledKey = "CLLocationServicesEnabled"
static let authorizationStatusKey = "CLAuthorizationStatus"
static let isMutuallyExclusive = false
let usage: Usage
init(usage: Usage) {
self.usage = usage
}
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return LocationPermissionOperation(usage: usage)
}
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
let enabled = CLLocationManager.locationServicesEnabled()
let actual = CLLocationManager().authorizationStatus
var error: NSError?
// There are several factors to consider when evaluating this condition
switch (enabled, usage, actual) {
case (true, _, .authorizedAlways):
// The service is enabled, and we have "Always" permission -> condition satisfied.
break
case (true, .WhenInUse, .authorizedWhenInUse):
/*
The service is enabled, and we have and need "WhenInUse"
permission -> condition satisfied.
*/
break
default:
/*
Anything else is an error. Maybe location services are disabled,
or maybe we need "Always" permission but only have "WhenInUse",
or maybe access has been restricted or denied,
or maybe access hasn't been request yet.
The last case would happen if this condition were wrapped in a `SilentCondition`.
*/
error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: type(of: self).name,
type(of: self).locationServicesEnabledKey: enabled,
type(of: self).authorizationStatusKey: Int(actual.rawValue)
])
}
if let error = error {
completion(.Failed(error))
}
else {
completion(.Satisfied)
}
}
}
/**
A private `EarthquakeOperation` that will request permission to access the user's location,
if permission has not already been granted.
*/
private class LocationPermissionOperation: EarthquakeOperation {
let usage: LocationCondition.Usage
var manager: CLLocationManager?
init(usage: LocationCondition.Usage) {
self.usage = usage
super.init()
/*
This is an operation that potentially presents an alert so it should
be mutually exclusive with anything else that presents an alert.
*/
addCondition(condition: AlertPresentation())
}
override func execute() {
/*
Not only do we need to handle the "Not Determined" case, but we also
need to handle the "upgrade" (.WhenInUse -> .Always) case.
*/
switch (CLLocationManager().authorizationStatus, usage) {
case (.notDetermined, _), (.authorizedWhenInUse, .Always):
DispatchQueue.main.async {
self.requestPermission()
}
default:
finish()
}
}
private func requestPermission() {
manager = CLLocationManager()
manager?.delegate = self
let key: String
switch usage {
case .WhenInUse:
key = "NSLocationWhenInUseUsageDescription"
manager?.requestWhenInUseAuthorization()
case .Always:
key = "NSLocationAlwaysUsageDescription"
manager?.requestAlwaysAuthorization()
}
// This is helpful when developing the app.
assert(Bundle.main.object(forInfoDictionaryKey: key) != nil, "Requesting location permission requires the \(key) key in your Info.plist")
}
}
extension LocationPermissionOperation: CLLocationManagerDelegate {
@available(iOS 14.0, *)
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if manager == self.manager && isExecuting && manager.authorizationStatus != .notDetermined {
finish()
}
}
}