Skip to content

Commit 5a85d38

Browse files
authored
Fix authorization upgrade issue; Fixes #16
1 parent b8b33f2 commit 5a85d38

File tree

4 files changed

+238
-94
lines changed

4 files changed

+238
-94
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.2.4"
1+
github "mxcl/PromiseKit" "6.2.5"

Sources/CLGeocoder+AnyPromise.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ - (AnyPromise *)geocode:(id)address {
2020
resolve(error ?: PMKManifold(placemarks.firstObject, placemarks));
2121
};
2222
if ([address isKindOfClass:[NSDictionary class]]) {
23+
#pragma clang diagnostic push
24+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
2325
[self geocodeAddressDictionary:address completionHandler:handler];
26+
#pragma clang diagnostic pop
2427
} else {
2528
[self geocodeAddressString:address completionHandler:handler];
2629
}

Sources/CLLocationManager+Promise.swift

Lines changed: 208 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import CoreLocation.CLLocationManager
33
import PromiseKit
44
#endif
55

6-
#if !os(tvOS)
7-
86
/**
97
To import the `CLLocationManager` category:
108

@@ -17,7 +15,7 @@ import PromiseKit
1715
*/
1816
extension CLLocationManager {
1917

20-
/// The location authorization type
18+
/// The type of location permission we are asking for
2119
public enum RequestAuthorizationType {
2220
/// Determine the authorization from the application’s plist
2321
case automatic
@@ -27,41 +25,76 @@ extension CLLocationManager {
2725
case whenInUse
2826
}
2927

30-
/// Request a single location using promises
31-
/// - Note: To return all locations call `allResults()`.
32-
///
33-
/// - Parameters:
34-
/// - authorizationType: requestAuthorizationType: We read your Info plist and try to
35-
/// determine the authorization type we should request automatically. If you
36-
/// want to force one or the other, change this parameter from its default
37-
/// value.
38-
/// - block: A block by which to perform any filtering of the locations
39-
/// that are returned. For example:
40-
/// - In order to only retrieve accurate locations, only
41-
/// return true if the locations horizontal accuracy < 50
42-
///
43-
/// - Returns: A new promise that fulfills with the most recent CLLocation
44-
/// that satisfies the provided block if it exists. If the block
45-
/// does not exist, simply return the last location.
28+
public enum PMKError: Error {
29+
case notAuthorized
30+
}
31+
32+
/**
33+
Request the current location.
34+
- Note: to obtain a single location use `Promise.lastValue`
35+
- Parameters:
36+
- authorizationType: requestAuthorizationType: We read your Info plist and try to
37+
determine the authorization type we should request automatically. If you
38+
want to force one or the other, change this parameter from its default
39+
value.
40+
- block: A block by which to perform any filtering of the locations that are
41+
returned. In order to only retrieve accurate locations, only return true if the
42+
locations horizontal accuracy < 50
43+
- Returns: A new promise that fulfills with the most recent CLLocation that satisfies
44+
the provided block if it exists. If the block does not exist, simply return the
45+
last location.
46+
*/
4647
public class func requestLocation(authorizationType: RequestAuthorizationType = .automatic, satisfying block: ((CLLocation) -> Bool)? = nil) -> Promise<[CLLocation]> {
47-
return promise(yielding: auther(authorizationType), satisfying: block)
48+
49+
func std() -> Promise<[CLLocation]> {
50+
return LocationManager(satisfying: block).promise
51+
}
52+
53+
func auth() -> Promise<Void> {
54+
#if os(macOS)
55+
return Promise()
56+
#else
57+
func auth(type: PMKCLAuthorizationType) -> Promise<Void> {
58+
return AuthorizationCatcher(type: type).promise.done(on: nil) {
59+
switch $0 {
60+
case .restricted, .denied:
61+
throw PMKError.notAuthorized
62+
default:
63+
break
64+
}
65+
}
66+
}
67+
68+
switch authorizationType {
69+
case .automatic:
70+
switch Bundle.main.permissionType {
71+
case .always, .both:
72+
return auth(type: .always)
73+
case .whenInUse:
74+
return auth(type: .whenInUse)
75+
}
76+
case .whenInUse:
77+
return auth(type: .whenInUse)
78+
case .always:
79+
return auth(type: .always)
80+
}
81+
#endif
82+
}
83+
84+
switch CLLocationManager.authorizationStatus() {
85+
case .authorizedAlways, .authorizedWhenInUse:
86+
return std()
87+
case .notDetermined:
88+
return auth().then(std)
89+
case .denied, .restricted:
90+
return Promise(error: PMKError.notAuthorized)
91+
}
4892
}
4993

5094
@available(*, deprecated: 5.0, renamed: "requestLocation")
5195
public class func promise(_ requestAuthorizationType: RequestAuthorizationType = .automatic, satisfying block: ((CLLocation) -> Bool)? = nil) -> Promise<[CLLocation]> {
5296
return requestLocation(authorizationType: requestAuthorizationType, satisfying: block)
5397
}
54-
55-
private class func promise(yielding yield: (CLLocationManager) -> Void = { _ in }, satisfying block: ((CLLocation) -> Bool)? = nil) -> Promise<[CLLocation]> {
56-
let manager = LocationManager(satisfying: block)
57-
manager.delegate = manager
58-
yield(manager)
59-
manager.startUpdatingLocation()
60-
_ = manager.promise.ensure {
61-
manager.stopUpdatingLocation()
62-
}
63-
return manager.promise
64-
}
6598
}
6699

67100
private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
@@ -71,16 +104,30 @@ private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
71104
@objc fileprivate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
72105
if let block = satisfyingBlock {
73106
let satisfiedLocations = locations.filter(block)
74-
if satisfiedLocations.count > 0 {
107+
if !satisfiedLocations.isEmpty {
75108
seal.fulfill(satisfiedLocations)
109+
} else {
110+
#if os(tvOS)
111+
requestLocation()
112+
#endif
76113
}
77114
} else {
78115
seal.fulfill(locations)
79116
}
80117
}
81118

82119
init(satisfying block: ((CLLocation) -> Bool)? = nil) {
83-
self.satisfyingBlock = block
120+
satisfyingBlock = block
121+
super.init()
122+
delegate = self
123+
#if !os(tvOS)
124+
startUpdatingLocation()
125+
#else
126+
requestLocation()
127+
#endif
128+
_ = self.promise.ensure {
129+
self.stopUpdatingLocation()
130+
}
84131
}
85132

86133
@objc func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
@@ -94,39 +141,120 @@ private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
94141
}
95142

96143

97-
#if os(iOS) || os(watchOS)
144+
#if !os(macOS)
98145

99146
extension CLLocationManager {
100-
/// request CoreLocation authorization from user
101-
/// NOTE if you want to do whenInUse -> always upgrades then you must specify the auth-type yourself.
102-
@available(iOS 8, *)
103-
public class func requestAuthorization(type: RequestAuthorizationType = .automatic) -> Guarantee<CLAuthorizationStatus> {
104-
return AuthorizationCatcher(auther: auther(type), type: type).promise
147+
/**
148+
Request CoreLocation authorization from the user
149+
- Note: By default we try to determine the authorization type you want by inspecting your Info.plist
150+
- Note: This method will not perform upgrades from “when-in-use” to “always” unless you specify `.always` for the value of `type`.
151+
*/
152+
@available(iOS 8, tvOS 9, watchOS 2, *)
153+
public class func requestAuthorization(type requestedAuthorizationType: RequestAuthorizationType = .automatic) -> Guarantee<CLAuthorizationStatus> {
154+
155+
let currentStatus = CLLocationManager.authorizationStatus()
156+
157+
func std(type: PMKCLAuthorizationType) -> Guarantee<CLAuthorizationStatus> {
158+
if currentStatus == .notDetermined {
159+
return AuthorizationCatcher(type: type).promise
160+
} else {
161+
return .value(currentStatus)
162+
}
163+
}
164+
165+
switch requestedAuthorizationType {
166+
case .always:
167+
func iOS11Check() -> Guarantee<CLAuthorizationStatus> {
168+
switch currentStatus {
169+
case .notDetermined, .authorizedWhenInUse:
170+
return AuthorizationCatcher(type: .always).promise
171+
default:
172+
return .value(currentStatus)
173+
}
174+
}
175+
#if PMKiOS11
176+
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
177+
// otherwise the warning you get below cannot be removed
178+
return iOS11Check()
179+
#else
180+
if #available(iOS 11, *) {
181+
return iOS11Check()
182+
} else {
183+
return std(type: .always)
184+
}
185+
#endif
186+
187+
case .whenInUse:
188+
return std(type: .whenInUse)
189+
190+
case .automatic:
191+
if currentStatus == .notDetermined {
192+
switch Bundle.main.permissionType {
193+
case .both, .whenInUse:
194+
return AuthorizationCatcher(type: .whenInUse).promise
195+
case .always:
196+
return AuthorizationCatcher(type: .always).promise
197+
}
198+
} else {
199+
return .value(currentStatus)
200+
}
201+
}
105202
}
106203
}
107204

205+
@available(iOS 8, *)
108206
private class AuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate {
109207
let (promise, fulfill) = Guarantee<CLAuthorizationStatus>.pending()
110208
var retainCycle: AuthorizationCatcher?
111209
let initialAuthorizationState = CLLocationManager.authorizationStatus()
112210

113-
init(auther: (CLLocationManager) -> Void, type: CLLocationManager.RequestAuthorizationType) {
211+
init(type: PMKCLAuthorizationType) {
114212
super.init()
115-
switch (initialAuthorizationState, type) {
116-
case (.authorizedWhenInUse, .always), (.authorizedWhenInUse, .automatic):
117-
if #available(iOS 11.0, *) {
118-
fallthrough
119-
}
120-
case (.notDetermined, _):
213+
214+
func ask(type: PMKCLAuthorizationType) {
121215
delegate = self
122-
auther(self)
123216
retainCycle = self
124-
default:
125-
fulfill(initialAuthorizationState)
217+
218+
switch type {
219+
case .always:
220+
#if os(tvOS)
221+
fallthrough
222+
#else
223+
requestAlwaysAuthorization()
224+
#endif
225+
case .whenInUse:
226+
requestWhenInUseAuthorization()
227+
}
228+
229+
promise.done { _ in
230+
self.retainCycle = nil
231+
}
232+
}
233+
234+
func iOS11Check() {
235+
switch (initialAuthorizationState, type) {
236+
case (.notDetermined, .always), (.authorizedWhenInUse, .always), (.notDetermined, .whenInUse):
237+
ask(type: type)
238+
default:
239+
fulfill(initialAuthorizationState)
240+
}
126241
}
127-
promise.done { _ in
128-
self.retainCycle = nil
242+
243+
#if PMKiOS11
244+
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
245+
// otherwise the warning you get below cannot be removed
246+
iOS11Check()
247+
#else
248+
if #available(iOS 11, *) {
249+
iOS11Check()
250+
} else {
251+
if initialAuthorizationState == .notDetermined {
252+
ask(type: type)
253+
} else {
254+
fulfill(initialAuthorizationState)
255+
}
129256
}
257+
#endif
130258
}
131259

132260
@objc fileprivate func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
@@ -137,42 +265,43 @@ private class AuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate
137265
}
138266
}
139267

140-
private func auther(_ requestAuthorizationType: CLLocationManager.RequestAuthorizationType) -> ((CLLocationManager) -> Void) {
268+
#endif
141269

142-
//PMKiOS7 guard #available(iOS 8, *) else { return }
143-
return { manager in
270+
private extension Bundle {
271+
enum PermissionType {
272+
case both
273+
case always
274+
case whenInUse
275+
}
276+
277+
var permissionType: PermissionType {
144278
func hasInfoPlistKey(_ key: String) -> Bool {
145-
let value = Bundle.main.object(forInfoDictionaryKey: key) as? String ?? ""
279+
let value = object(forInfoDictionaryKey: key) as? String ?? ""
146280
return !value.isEmpty
147281
}
148282

149-
switch requestAuthorizationType {
150-
case .automatic:
151-
let always = hasInfoPlistKey("NSLocationAlwaysUsageDescription") || hasInfoPlistKey("NSLocationAlwaysAndWhenInUseUsageDescription")
152-
let whenInUse = { hasInfoPlistKey("NSLocationWhenInUseUsageDescription") }
153-
if always {
154-
manager.requestAlwaysAuthorization()
155-
} else {
156-
if !whenInUse() { NSLog("PromiseKit: Warning: `NSLocationAlwaysAndWhenInUseUsageDescription` key not set") }
157-
manager.requestWhenInUseAuthorization()
158-
}
159-
case .whenInUse:
160-
manager.requestWhenInUseAuthorization()
161-
break
162-
case .always:
163-
manager.requestAlwaysAuthorization()
164-
break
283+
if hasInfoPlistKey("NSLocationAlwaysAndWhenInUseUsageDescription") {
284+
return .both
285+
}
286+
if hasInfoPlistKey("NSLocationAlwaysUsageDescription") {
287+
return .always
288+
}
289+
if hasInfoPlistKey("NSLocationWhenInUseUsageDescription") {
290+
return .whenInUse
291+
}
165292

293+
if #available(iOS 11, *) {
294+
NSLog("PromiseKit: warning: `NSLocationAlwaysAndWhenInUseUsageDescription` key not set")
295+
} else {
296+
NSLog("PromiseKit: warning: `NSLocationWhenInUseUsageDescription` key not set")
166297
}
298+
299+
// won't work, but we warned the user above at least
300+
return .whenInUse
167301
}
168302
}
169303

170-
#else
171-
172-
private func auther(_ requestAuthorizationType: CLLocationManager.RequestAuthorizationType) -> (CLLocationManager) -> Void {
173-
return { _ in }
304+
private enum PMKCLAuthorizationType {
305+
case always
306+
case whenInUse
174307
}
175-
176-
#endif
177-
178-
#endif

0 commit comments

Comments
 (0)