Add PromiseKit dependency

- Added PromiseKit dependency
This commit is contained in:
2018-11-15 22:08:00 -04:00
parent 2689d86c18
commit be7b6b5881
541 changed files with 46282 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
#import <CoreLocation/CLGeocoder.h>
#import <PromiseKit/AnyPromise.h>
/**
To import the `CLGecoder` category:
use_frameworks!
pod "PromiseKit/CoreLocation"
And then in your sources:
@import PromiseKit;
*/
@interface CLGeocoder (PromiseKit)
/**
Submits a reverse-geocoding request for the specified location.
@param location The location object containing the coordinate data to look up.
@return A promise that thens two parameters:
1. The first placemark that resides at the specified location.
2. The array of *all* placemarks that reside at the specified location.
*/
- (AnyPromise *)reverseGeocode:(CLLocation *)location NS_REFINED_FOR_SWIFT;
/**
Submits a forward-geocoding request using the specified address dictionary or address string.
@param addressDictionaryOrAddressString The address dictionary or address string to look up.
@return A promise that thens two parameters:
1. The first placemark that resides at the specified address.
2. The array of *all* placemarks that reside at the specified address.
*/
- (AnyPromise *)geocode:(id)addressDictionaryOrAddressString NS_REFINED_FOR_SWIFT;
@end

View File

@@ -0,0 +1,47 @@
#import "CLGeocoder+AnyPromise.h"
#import <CoreLocation/CLError.h>
#import <CoreLocation/CLErrorDomain.h>
#import <PromiseKit/AnyPromise.h>
@implementation CLGeocoder (PromiseKit)
- (AnyPromise *)reverseGeocode:(CLLocation *)location {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
resolve(error ?: PMKManifold(placemarks.firstObject, placemarks));
}];
}];
}
- (AnyPromise *)geocode:(id)address {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
id handler = ^(NSArray *placemarks, NSError *error) {
resolve(error ?: PMKManifold(placemarks.firstObject, placemarks));
};
if ([address isKindOfClass:[NSDictionary class]]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self geocodeAddressDictionary:address completionHandler:handler];
#pragma clang diagnostic pop
} else {
[self geocodeAddressString:address completionHandler:handler];
}
}];
}
@end
@implementation CLGeocoder (PMKDeprecated)
+ (AnyPromise *)reverseGeocode:(CLLocation *)location {
return [[CLGeocoder new] reverseGeocode:location];
}
+ (AnyPromise *)geocode:(id)input {
return [[CLGeocoder new] geocode:input];
}
@end

View File

@@ -0,0 +1,73 @@
import CoreLocation.CLGeocoder
#if !PMKCocoaPods
import PromiseKit
#endif
#if os(iOS) || os(watchOS) || os(OSX)
import class Contacts.CNPostalAddress
#endif
/**
To import the `CLGeocoder` category:
use_frameworks!
pod "PromiseKit/CoreLocation"
And then in your sources:
import PromiseKit
*/
extension CLGeocoder {
/// Submits a reverse-geocoding request for the specified location.
public func reverseGeocode(location: CLLocation) -> Promise<[CLPlacemark]> {
return Promise { seal in
reverseGeocodeLocation(location, completionHandler: seal.resolve)
}
}
/// Submits a forward-geocoding request using the specified address dictionary.
@available(iOS, deprecated: 11.0)
public func geocode(_ addressDictionary: [String: String]) -> Promise<[CLPlacemark]> {
return Promise { seal in
geocodeAddressDictionary(addressDictionary, completionHandler: seal.resolve)
}
}
/// Submits a forward-geocoding request using the specified address string.
public func geocode(_ addressString: String) -> Promise<[CLPlacemark]> {
return Promise { seal in
geocodeAddressString(addressString, completionHandler: seal.resolve)
}
}
/// Submits a forward-geocoding request using the specified address string within the specified region.
public func geocode(_ addressString: String, region: CLRegion?) -> Promise<[CLPlacemark]> {
return Promise { 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 geocodePostalAddress(_ postalAddress: CNPostalAddress) -> Promise<[CLPlacemark]> {
return Promise { 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 geocodePostalAddress(_ postalAddress: CNPostalAddress, preferredLocale locale: Locale?) -> Promise<[CLPlacemark]> {
return Promise { seal in
geocodePostalAddress(postalAddress, preferredLocale: locale, completionHandler: seal.resolve)
}
}
#endif
}
// TODO still not possible in Swift 3.2
//extension CLError: CancellableError {
// public var isCancelled: Bool {
// return self == .geocodeCanceled
// }
//}

View File

@@ -0,0 +1,46 @@
#import <CoreLocation/CLLocationManager.h>
#import <PromiseKit/AnyPromise.h>
/**
To import the `CLLocationManager` category:
use_frameworks!
pod "PromiseKit/CoreLocation"
And then in your sources:
@import PromiseKit;
*/
@interface CLLocationManager (PromiseKit)
/**
Determines the devices location waiting until the positional accuracy
of the measured locations is better than 500 meters.
If your app has not yet asked the user for locational determination
permissions, PromiseKit calls `+requestWhenInUseAuthorization`, if
you need always permissions, you must call this yourself before
any use of this method, or the promise will be rejected.
@return A promise that thens two parameters:
1. The most recent `CLLocation`.
2. An array of all recent `CLLocations`.
*/
+ (AnyPromise *)promise NS_REFINED_FOR_SWIFT;
/**
Determines the devices location using the provided block to determine
when locations become acceptably accurate.
With this variant you can wait for good accuracy or acceptable accuracy
(at your own discretion) if the `CLLocationManager` is taking too
long. For example, the user is not outside so you will never get 10 meter
accuracy, but it would be nice to wait a little just in case.
- see +promise
*/
+ (AnyPromise *)until:(BOOL(^)(CLLocation *))isLocationGoodBlock NS_REFINED_FOR_SWIFT;
@end

View File

@@ -0,0 +1,72 @@
#import <CoreLocation/CLLocationManagerDelegate.h>
#import "CLLocationManager+AnyPromise.h"
#import <CoreLocation/CLError.h>
#if !(TARGET_OS_TV && (TARGET_OS_EMBEDDED || TARGET_OS_SIMULATOR))
@interface PMKLocationManager : CLLocationManager <CLLocationManagerDelegate> {
@public
PMKResolver resolve;
id retainCycle;
BOOL (^block)(CLLocation *);
}
@end
@implementation PMKLocationManager
#define PMKLocationManagerCleanup() \
[manager stopUpdatingLocation]; \
retainCycle = self.delegate = nil;
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSMutableArray *okd = [NSMutableArray new];
for (id location in locations)
if (block(location))
[okd addObject:location];
if (okd.count) {
resolve(PMKManifold(okd.lastObject, okd));
PMKLocationManagerCleanup();
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
// Apple docs say to ignore this error
if (error.code != kCLErrorLocationUnknown) {
resolve(error);
PMKLocationManagerCleanup();
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
[manager startUpdatingLocation];
}
@end
@implementation CLLocationManager (PromiseKit)
+ (AnyPromise *)promise {
return [self until:^BOOL(CLLocation *location){
return location.horizontalAccuracy <= 500 && location.verticalAccuracy <= 500;
}];
}
+ (AnyPromise *)until:(BOOL(^)(CLLocation *))block {
PMKLocationManager *manager = [PMKLocationManager new];
manager.delegate = manager;
manager->block = block;
manager->retainCycle = manager;
#if TARGET_OS_IPHONE
if ([manager respondsToSelector:@selector(requestWhenInUseAuthorization)])
[manager requestWhenInUseAuthorization];
#endif
[manager startUpdatingLocation];
return [[AnyPromise alloc] initWithResolver:&manager->resolve];
}
@end
#endif

View File

@@ -0,0 +1,307 @@
import CoreLocation.CLLocationManager
#if !PMKCocoaPods
import PromiseKit
#endif
/**
To import the `CLLocationManager` category:
use_frameworks!
pod "PromiseKit/CoreLocation"
And then in your sources:
import PromiseKit
*/
extension CLLocationManager {
/// The type of location permission we are asking for
public enum RequestAuthorizationType {
/// Determine the authorization from the applications plist
case automatic
/// Request always-authorization
case always
/// Request when-in-use-authorization
case whenInUse
}
public enum PMKError: Error {
case notAuthorized
}
/**
Request the current location.
- 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 requestLocation(authorizationType: RequestAuthorizationType = .automatic, satisfying block: ((CLLocation) -> Bool)? = nil) -> Promise<[CLLocation]> {
func std() -> Promise<[CLLocation]> {
return LocationManager(satisfying: block).promise
}
func auth() -> Promise<Void> {
#if os(macOS)
return Promise()
#else
func auth(type: PMKCLAuthorizationType) -> Promise<Void> {
return AuthorizationCatcher(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 Promise(error: PMKError.notAuthorized)
}
}
@available(*, deprecated: 5.0, renamed: "requestLocation")
public class func promise(_ requestAuthorizationType: RequestAuthorizationType = .automatic, satisfying block: ((CLLocation) -> Bool)? = nil) -> Promise<[CLLocation]> {
return requestLocation(authorizationType: requestAuthorizationType, satisfying: block)
}
}
private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
let (promise, seal) = Promise<[CLLocation]>.pending()
let satisfyingBlock: ((CLLocation) -> Bool)?
@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)
}
}
init(satisfying block: ((CLLocation) -> Bool)? = nil) {
satisfyingBlock = block
super.init()
delegate = self
#if !os(tvOS)
startUpdatingLocation()
#else
requestLocation()
#endif
_ = self.promise.ensure {
self.stopUpdatingLocation()
}
}
@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)
}
}
}
#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 requestAuthorization(type requestedAuthorizationType: RequestAuthorizationType = .automatic) -> Guarantee<CLAuthorizationStatus> {
let currentStatus = CLLocationManager.authorizationStatus()
func std(type: PMKCLAuthorizationType) -> Guarantee<CLAuthorizationStatus> {
if currentStatus == .notDetermined {
return AuthorizationCatcher(type: type).promise
} else {
return .value(currentStatus)
}
}
switch requestedAuthorizationType {
case .always:
func iOS11Check() -> Guarantee<CLAuthorizationStatus> {
switch currentStatus {
case .notDetermined, .authorizedWhenInUse:
return AuthorizationCatcher(type: .always).promise
default:
return .value(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 AuthorizationCatcher(type: .whenInUse).promise
case .always:
return AuthorizationCatcher(type: .always).promise
}
} else {
return .value(currentStatus)
}
}
}
}
@available(iOS 8, *)
private class AuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate {
let (promise, fulfill) = Guarantee<CLAuthorizationStatus>.pending()
var retainCycle: AuthorizationCatcher?
let initialAuthorizationState = CLLocationManager.authorizationStatus()
init(type: PMKCLAuthorizationType) {
super.init()
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:
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 {
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 {
fulfill(status)
}
}
}
#endif
private extension Bundle {
enum PermissionType {
case both
case always
case whenInUse
}
var permissionType: PermissionType {
func hasInfoPlistKey(_ key: String) -> Bool {
let value = object(forInfoDictionaryKey: key) as? String ?? ""
return !value.isEmpty
}
if hasInfoPlistKey("NSLocationAlwaysAndWhenInUseUsageDescription") {
return .both
}
if hasInfoPlistKey("NSLocationAlwaysUsageDescription") {
return .always
}
if hasInfoPlistKey("NSLocationWhenInUseUsageDescription") {
return .whenInUse
}
if #available(iOS 11, *) {
NSLog("PromiseKit: warning: `NSLocationAlwaysAndWhenInUseUsageDescription` key not set")
} else {
NSLog("PromiseKit: warning: `NSLocationWhenInUseUsageDescription` key not set")
}
// won't work, but we warned the user above at least
return .whenInUse
}
}
private enum PMKCLAuthorizationType {
case always
case whenInUse
}

View File

@@ -0,0 +1,2 @@
#import "CLLocationManager+AnyPromise.h"
#import "CLGeocoder+AnyPromise.h"