Skip to content

'Cancel' for PromiseKit option 2 #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
github "mxcl/PromiseKit" ~> 6.0
#github "mxcl/PromiseKit" ~> 6.0
github "dougzilla32/PromiseKit" "PMKCancel"
#github "PromiseKit/Cancel" ~> 1.0
github "dougzilla32/Cancel" ~> 1.0
3 changes: 2 additions & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
github "mxcl/PromiseKit" "6.3.3"
github "dougzilla32/Cancel" "1.0.0"
github "dougzilla32/PromiseKit" "a0217bd7b69af68237dcdeee0197e63259b9d445"
1 change: 1 addition & 0 deletions PMKCoreLocation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@
);
inputPaths = (
PromiseKit,
PMKCancel,
);
name = "Embed Carthage Frameworks";
outputPaths = (
Expand Down
52 changes: 52 additions & 0 deletions Sources/CLGeocoder+Promise.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import CoreLocation.CLGeocoder
#if !PMKCocoaPods
import PMKCancel
import PromiseKit
#endif
#if os(iOS) || os(watchOS) || os(OSX)
Expand Down Expand Up @@ -71,3 +72,54 @@ extension CLGeocoder {
// return self == .geocodeCanceled
// }
//}

//////////////////////////////////////////////////////////// Cancellation

extension CLGeocoder {
/// Submits a reverse-geocoding request for the specified location.
public func reverseGeocodeCC(location: CLLocation) -> CancellablePromise<[CLPlacemark]> {
return CancellablePromise { seal in
reverseGeocodeLocation(location, completionHandler: seal.resolve)
}
}

/// Submits a forward-geocoding request using the specified address dictionary.
@available(iOS, deprecated: 11.0)
public func geocodeCC(_ addressDictionary: [String: String]) -> CancellablePromise<[CLPlacemark]> {
return CancellablePromise { seal in
geocodeAddressDictionary(addressDictionary, completionHandler: seal.resolve)
}
}

/// Submits a forward-geocoding request using the specified address string.
public func geocodeCC(_ addressString: String) -> CancellablePromise<[CLPlacemark]> {
return CancellablePromise { seal in
geocodeAddressString(addressString, completionHandler: seal.resolve)
}
}

/// Submits a forward-geocoding request using the specified address string within the specified region.
public func geocodeCC(_ addressString: String, region: CLRegion?) -> CancellablePromise<[CLPlacemark]> {
return CancellablePromise { seal in
geocodeAddressString(addressString, in: region, completionHandler: seal.resolve)
}
}

#if !os(tvOS) && swift(>=3.2)
/// Submits a forward-geocoding request using the specified postal address.
@available(iOS 11.0, OSX 10.13, watchOS 4.0, *)
public func geocodePostalAddressCC(_ postalAddress: CNPostalAddress) -> CancellablePromise<[CLPlacemark]> {
return CancellablePromise { seal in
geocodePostalAddress(postalAddress, completionHandler: seal.resolve)
}
}

/// Submits a forward-geocoding requesting using the specified locale and postal address
@available(iOS 11.0, OSX 10.13, watchOS 4.0, *)
public func geocodePostalAddressCC(_ postalAddress: CNPostalAddress, preferredLocale locale: Locale?) -> CancellablePromise<[CLPlacemark]> {
return CancellablePromise { seal in
geocodePostalAddress(postalAddress, preferredLocale: locale, completionHandler: seal.resolve)
}
}
#endif
}
255 changes: 255 additions & 0 deletions Sources/CLLocationManager+Promise.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import CoreLocation.CLLocationManager
#if !PMKCocoaPods
import PMKCancel
import PromiseKit
#endif

Expand Down Expand Up @@ -305,3 +306,257 @@ private enum PMKCLAuthorizationType {
case always
case whenInUse
}

//////////////////////////////////////////////////////////// Cancellation

extension CLLocationManager {
/**
Request the current location, with the ability to cancel the request.
- Note: to obtain a single location use `Promise.lastValue`
- Parameters:
- authorizationType: requestAuthorizationType: We read your Info plist and try to
determine the authorization type we should request automatically. If you
want to force one or the other, change this parameter from its default
value.
- block: A block by which to perform any filtering of the locations that are
returned. In order to only retrieve accurate locations, only return true if the
locations horizontal accuracy < 50
- Returns: A new promise that fulfills with the most recent CLLocation that satisfies
the provided block if it exists. If the block does not exist, simply return the
last location.
*/
public class func requestLocationCC(authorizationType: RequestAuthorizationType = .automatic, satisfying block: ((CLLocation) -> Bool)? = nil) -> CancellablePromise<[CLLocation]> {

func std() -> CancellablePromise<[CLLocation]> {
return CancellableLocationManager(satisfying: block).promise
}

func auth() -> CancellablePromise<Void> {
#if os(macOS)
return CancellablePromise { seal in seal.fulfill(()) }
#else
func auth(type: PMKCLAuthorizationType) -> CancellablePromise<Void> {
return CancellableAuthorizationCatcher(type: type).promise.done(on: nil) {
switch $0 {
case .restricted, .denied:
throw PMKError.notAuthorized
default:
break
}
}
}

switch authorizationType {
case .automatic:
switch Bundle.main.permissionType {
case .always, .both:
return auth(type: .always)
case .whenInUse:
return auth(type: .whenInUse)
}
case .whenInUse:
return auth(type: .whenInUse)
case .always:
return auth(type: .always)
}
#endif
}

switch CLLocationManager.authorizationStatus() {
case .authorizedAlways, .authorizedWhenInUse:
return std()
case .notDetermined:
return auth().then(std)
case .denied, .restricted:
return CancellablePromise(error: PMKError.notAuthorized)
}
}
}

private class CancellableLocationManager: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
let (promise, seal) = CancellablePromise<[CLLocation]>.pending()
let satisfyingBlock: ((CLLocation) -> Bool)?

init(satisfying block: ((CLLocation) -> Bool)? = nil) {
satisfyingBlock = block
super.init()
delegate = self

promise.appendCancellableTask(task: self, reject: seal.reject)

#if !os(tvOS)
startUpdatingLocation()
#else
requestLocation()
#endif
_ = self.promise.ensure {
self.stopUpdatingLocation()
}
}

@objc fileprivate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let block = satisfyingBlock {
let satisfiedLocations = locations.filter(block)
if !satisfiedLocations.isEmpty {
seal.fulfill(satisfiedLocations)
} else {
#if os(tvOS)
requestLocation()
#endif
}
} else {
seal.fulfill(locations)
}
}

@objc func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
let (domain, code) = { ($0.domain, $0.code) }(error as NSError)
if code == CLError.locationUnknown.rawValue && domain == kCLErrorDomain {
// Apple docs say you should just ignore this error
} else {
seal.reject(error)
}
}

func cancel() {
self.stopUpdatingLocation()
isCancelled = true
}

var isCancelled = false
}

#if !os(macOS)

extension CLLocationManager {
/**
Request CoreLocation authorization from the user
- Note: By default we try to determine the authorization type you want by inspecting your Info.plist
- Note: This method will not perform upgrades from “when-in-use” to “always” unless you specify `.always` for the value of `type`.
*/
@available(iOS 8, tvOS 9, watchOS 2, *)
public class func requestAuthorizationCC(type requestedAuthorizationType: RequestAuthorizationType = .automatic) -> CancellablePromise<CLAuthorizationStatus> {

let currentStatus = CLLocationManager.authorizationStatus()

func std(type: PMKCLAuthorizationType) -> CancellablePromise<CLAuthorizationStatus> {
if currentStatus == .notDetermined {
return CancellableAuthorizationCatcher(type: type).promise
} else {
return .valueCC(currentStatus)
}
}

switch requestedAuthorizationType {
case .always:
func iOS11Check() -> CancellablePromise<CLAuthorizationStatus> {
switch currentStatus {
case .notDetermined, .authorizedWhenInUse:
return CancellableAuthorizationCatcher(type: .always).promise
default:
return .valueCC(currentStatus)
}
}
#if PMKiOS11
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
// otherwise the warning you get below cannot be removed
return iOS11Check()
#else
if #available(iOS 11, *) {
return iOS11Check()
} else {
return std(type: .always)
}
#endif

case .whenInUse:
return std(type: .whenInUse)

case .automatic:
if currentStatus == .notDetermined {
switch Bundle.main.permissionType {
case .both, .whenInUse:
return CancellableAuthorizationCatcher(type: .whenInUse).promise
case .always:
return CancellableAuthorizationCatcher(type: .always).promise
}
} else {
return .valueCC(currentStatus)
}
}
}
}

@available(iOS 8, *)
private class CancellableAuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
let (promise, seal) = CancellablePromise<CLAuthorizationStatus>.pending()
var retainCycle: CancellableAuthorizationCatcher?
let initialAuthorizationState = CLLocationManager.authorizationStatus()

init(type: PMKCLAuthorizationType) {
super.init()

promise.appendCancellableTask(task: self, reject: seal.reject)

func ask(type: PMKCLAuthorizationType) {
delegate = self
retainCycle = self

switch type {
case .always:
#if os(tvOS)
fallthrough
#else
requestAlwaysAuthorization()
#endif
case .whenInUse:
requestWhenInUseAuthorization()
}

_ = promise.done { _ in
self.retainCycle = nil
}
}

func iOS11Check() {
switch (initialAuthorizationState, type) {
case (.notDetermined, .always), (.authorizedWhenInUse, .always), (.notDetermined, .whenInUse):
ask(type: type)
default:
seal.fulfill(initialAuthorizationState)
}
}

#if PMKiOS11
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
// otherwise the warning you get below cannot be removed
iOS11Check()
#else
if #available(iOS 11, *) {
iOS11Check()
} else {
if initialAuthorizationState == .notDetermined {
ask(type: type)
} else {
seal.fulfill(initialAuthorizationState)
}
}
#endif
}

@objc fileprivate func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// `didChange` is a lie; it fires this immediately with the current status.
if status != initialAuthorizationState {
seal.fulfill(status)
}
}

func cancel() {
self.retainCycle = nil
isCancelled = true
}

var isCancelled = false
}

#endif
Loading