@@ -305,3 +305,257 @@ private enum PMKCLAuthorizationType {
305
305
case always
306
306
case whenInUse
307
307
}
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