Skip to content

Commit 5c4a977

Browse files
committed
'Cancel' for PromiseKit
1 parent 1278bb2 commit 5c4a977

File tree

5 files changed

+477
-1
lines changed

5 files changed

+477
-1
lines changed

Cartfile.resolved

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
github "mxcl/PromiseKit" "6.3.3"
1+
github "mxcl/PromiseKit" "6.3.4"

Sources/CLGeocoder+Promise.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,54 @@ extension CLGeocoder {
7171
// return self == .geocodeCanceled
7272
// }
7373
//}
74+
75+
//////////////////////////////////////////////////////////// Cancellation
76+
77+
extension CLGeocoder {
78+
/// Submits a reverse-geocoding request for the specified location.
79+
public func reverseGeocodeCC(location: CLLocation) -> CancellablePromise<[CLPlacemark]> {
80+
return CancellablePromise { seal in
81+
reverseGeocodeLocation(location, completionHandler: seal.resolve)
82+
}
83+
}
84+
85+
/// Submits a forward-geocoding request using the specified address dictionary.
86+
@available(iOS, deprecated: 11.0)
87+
public func geocodeCC(_ addressDictionary: [String: String]) -> CancellablePromise<[CLPlacemark]> {
88+
return CancellablePromise { seal in
89+
geocodeAddressDictionary(addressDictionary, completionHandler: seal.resolve)
90+
}
91+
}
92+
93+
/// Submits a forward-geocoding request using the specified address string.
94+
public func geocodeCC(_ addressString: String) -> CancellablePromise<[CLPlacemark]> {
95+
return CancellablePromise { seal in
96+
geocodeAddressString(addressString, completionHandler: seal.resolve)
97+
}
98+
}
99+
100+
/// Submits a forward-geocoding request using the specified address string within the specified region.
101+
public func geocodeCC(_ addressString: String, region: CLRegion?) -> CancellablePromise<[CLPlacemark]> {
102+
return CancellablePromise { seal in
103+
geocodeAddressString(addressString, in: region, completionHandler: seal.resolve)
104+
}
105+
}
106+
107+
#if !os(tvOS) && swift(>=3.2)
108+
/// Submits a forward-geocoding request using the specified postal address.
109+
@available(iOS 11.0, OSX 10.13, watchOS 4.0, *)
110+
public func geocodePostalAddressCC(_ postalAddress: CNPostalAddress) -> CancellablePromise<[CLPlacemark]> {
111+
return CancellablePromise { seal in
112+
geocodePostalAddress(postalAddress, completionHandler: seal.resolve)
113+
}
114+
}
115+
116+
/// Submits a forward-geocoding requesting using the specified locale and postal address
117+
@available(iOS 11.0, OSX 10.13, watchOS 4.0, *)
118+
public func geocodePostalAddressCC(_ postalAddress: CNPostalAddress, preferredLocale locale: Locale?) -> CancellablePromise<[CLPlacemark]> {
119+
return CancellablePromise { seal in
120+
geocodePostalAddress(postalAddress, preferredLocale: locale, completionHandler: seal.resolve)
121+
}
122+
}
123+
#endif
124+
}

Sources/CLLocationManager+Promise.swift

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,257 @@ private enum PMKCLAuthorizationType {
305305
case always
306306
case whenInUse
307307
}
308+
309+
//////////////////////////////////////////////////////////// Cancellation
310+
311+
extension CLLocationManager {
312+
/**
313+
Request the current location, with the ability to cancel the request.
314+
- Note: to obtain a single location use `Promise.lastValue`
315+
- Parameters:
316+
- authorizationType: requestAuthorizationType: We read your Info plist and try to
317+
determine the authorization type we should request automatically. If you
318+
want to force one or the other, change this parameter from its default
319+
value.
320+
- block: A block by which to perform any filtering of the locations that are
321+
returned. In order to only retrieve accurate locations, only return true if the
322+
locations horizontal accuracy < 50
323+
- Returns: A new promise that fulfills with the most recent CLLocation that satisfies
324+
the provided block if it exists. If the block does not exist, simply return the
325+
last location.
326+
*/
327+
public class func requestLocationCC(authorizationType: RequestAuthorizationType = .automatic, satisfying block: ((CLLocation) -> Bool)? = nil) -> CancellablePromise<[CLLocation]> {
328+
329+
func std() -> CancellablePromise<[CLLocation]> {
330+
return CancellableLocationManager(satisfying: block).promise
331+
}
332+
333+
func auth() -> CancellablePromise<Void> {
334+
#if os(macOS)
335+
return CancellablePromise { seal in seal.fulfill(()) }
336+
#else
337+
func auth(type: PMKCLAuthorizationType) -> CancellablePromise<Void> {
338+
return CancellableAuthorizationCatcher(type: type).promise.done(on: nil) {
339+
switch $0 {
340+
case .restricted, .denied:
341+
throw PMKError.notAuthorized
342+
default:
343+
break
344+
}
345+
}
346+
}
347+
348+
switch authorizationType {
349+
case .automatic:
350+
switch Bundle.main.permissionType {
351+
case .always, .both:
352+
return auth(type: .always)
353+
case .whenInUse:
354+
return auth(type: .whenInUse)
355+
}
356+
case .whenInUse:
357+
return auth(type: .whenInUse)
358+
case .always:
359+
return auth(type: .always)
360+
}
361+
#endif
362+
}
363+
364+
switch CLLocationManager.authorizationStatus() {
365+
case .authorizedAlways, .authorizedWhenInUse:
366+
return std()
367+
case .notDetermined:
368+
return auth().then(std)
369+
case .denied, .restricted:
370+
return CancellablePromise(error: PMKError.notAuthorized)
371+
}
372+
}
373+
}
374+
375+
private class CancellableLocationManager: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
376+
let (promise, seal) = CancellablePromise<[CLLocation]>.pending()
377+
let satisfyingBlock: ((CLLocation) -> Bool)?
378+
379+
init(satisfying block: ((CLLocation) -> Bool)? = nil) {
380+
satisfyingBlock = block
381+
super.init()
382+
delegate = self
383+
384+
promise.appendCancellableTask(task: self, reject: seal.reject)
385+
386+
#if !os(tvOS)
387+
startUpdatingLocation()
388+
#else
389+
requestLocation()
390+
#endif
391+
_ = self.promise.ensure {
392+
self.stopUpdatingLocation()
393+
}
394+
}
395+
396+
@objc fileprivate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
397+
if let block = satisfyingBlock {
398+
let satisfiedLocations = locations.filter(block)
399+
if !satisfiedLocations.isEmpty {
400+
seal.fulfill(satisfiedLocations)
401+
} else {
402+
#if os(tvOS)
403+
requestLocation()
404+
#endif
405+
}
406+
} else {
407+
seal.fulfill(locations)
408+
}
409+
}
410+
411+
@objc func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
412+
let (domain, code) = { ($0.domain, $0.code) }(error as NSError)
413+
if code == CLError.locationUnknown.rawValue && domain == kCLErrorDomain {
414+
// Apple docs say you should just ignore this error
415+
} else {
416+
seal.reject(error)
417+
}
418+
}
419+
420+
func cancel() {
421+
self.stopUpdatingLocation()
422+
isCancelled = true
423+
}
424+
425+
var isCancelled = false
426+
}
427+
428+
#if !os(macOS)
429+
430+
extension CLLocationManager {
431+
/**
432+
Request CoreLocation authorization from the user
433+
- Note: By default we try to determine the authorization type you want by inspecting your Info.plist
434+
- Note: This method will not perform upgrades from “when-in-use” to “always” unless you specify `.always` for the value of `type`.
435+
*/
436+
@available(iOS 8, tvOS 9, watchOS 2, *)
437+
public class func requestAuthorizationCC(type requestedAuthorizationType: RequestAuthorizationType = .automatic) -> CancellablePromise<CLAuthorizationStatus> {
438+
439+
let currentStatus = CLLocationManager.authorizationStatus()
440+
441+
func std(type: PMKCLAuthorizationType) -> CancellablePromise<CLAuthorizationStatus> {
442+
if currentStatus == .notDetermined {
443+
return CancellableAuthorizationCatcher(type: type).promise
444+
} else {
445+
return .valueCC(currentStatus)
446+
}
447+
}
448+
449+
switch requestedAuthorizationType {
450+
case .always:
451+
func iOS11Check() -> CancellablePromise<CLAuthorizationStatus> {
452+
switch currentStatus {
453+
case .notDetermined, .authorizedWhenInUse:
454+
return CancellableAuthorizationCatcher(type: .always).promise
455+
default:
456+
return .valueCC(currentStatus)
457+
}
458+
}
459+
#if PMKiOS11
460+
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
461+
// otherwise the warning you get below cannot be removed
462+
return iOS11Check()
463+
#else
464+
if #available(iOS 11, *) {
465+
return iOS11Check()
466+
} else {
467+
return std(type: .always)
468+
}
469+
#endif
470+
471+
case .whenInUse:
472+
return std(type: .whenInUse)
473+
474+
case .automatic:
475+
if currentStatus == .notDetermined {
476+
switch Bundle.main.permissionType {
477+
case .both, .whenInUse:
478+
return CancellableAuthorizationCatcher(type: .whenInUse).promise
479+
case .always:
480+
return CancellableAuthorizationCatcher(type: .always).promise
481+
}
482+
} else {
483+
return .valueCC(currentStatus)
484+
}
485+
}
486+
}
487+
}
488+
489+
@available(iOS 8, *)
490+
private class CancellableAuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
491+
let (promise, seal) = CancellablePromise<CLAuthorizationStatus>.pending()
492+
var retainCycle: CancellableAuthorizationCatcher?
493+
let initialAuthorizationState = CLLocationManager.authorizationStatus()
494+
495+
init(type: PMKCLAuthorizationType) {
496+
super.init()
497+
498+
promise.appendCancellableTask(task: self, reject: seal.reject)
499+
500+
func ask(type: PMKCLAuthorizationType) {
501+
delegate = self
502+
retainCycle = self
503+
504+
switch type {
505+
case .always:
506+
#if os(tvOS)
507+
fallthrough
508+
#else
509+
requestAlwaysAuthorization()
510+
#endif
511+
case .whenInUse:
512+
requestWhenInUseAuthorization()
513+
}
514+
515+
_ = promise.done { _ in
516+
self.retainCycle = nil
517+
}
518+
}
519+
520+
func iOS11Check() {
521+
switch (initialAuthorizationState, type) {
522+
case (.notDetermined, .always), (.authorizedWhenInUse, .always), (.notDetermined, .whenInUse):
523+
ask(type: type)
524+
default:
525+
seal.fulfill(initialAuthorizationState)
526+
}
527+
}
528+
529+
#if PMKiOS11
530+
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
531+
// otherwise the warning you get below cannot be removed
532+
iOS11Check()
533+
#else
534+
if #available(iOS 11, *) {
535+
iOS11Check()
536+
} else {
537+
if initialAuthorizationState == .notDetermined {
538+
ask(type: type)
539+
} else {
540+
seal.fulfill(initialAuthorizationState)
541+
}
542+
}
543+
#endif
544+
}
545+
546+
@objc fileprivate func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
547+
// `didChange` is a lie; it fires this immediately with the current status.
548+
if status != initialAuthorizationState {
549+
seal.fulfill(status)
550+
}
551+
}
552+
553+
func cancel() {
554+
self.retainCycle = nil
555+
isCancelled = true
556+
}
557+
558+
var isCancelled = false
559+
}
560+
561+
#endif

0 commit comments

Comments
 (0)