@@ -3,8 +3,6 @@ import CoreLocation.CLLocationManager
3
3
import PromiseKit
4
4
#endif
5
5
6
- #if !os(tvOS)
7
-
8
6
/**
9
7
To import the `CLLocationManager` category:
10
8
@@ -17,7 +15,7 @@ import PromiseKit
17
15
*/
18
16
extension CLLocationManager {
19
17
20
- /// The location authorization type
18
+ /// The type of location permission we are asking for
21
19
public enum RequestAuthorizationType {
22
20
/// Determine the authorization from the application’s plist
23
21
case automatic
@@ -27,41 +25,76 @@ extension CLLocationManager {
27
25
case whenInUse
28
26
}
29
27
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
+ */
46
47
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
+ }
48
92
}
49
93
50
94
@available ( * , deprecated: 5.0 , renamed: " requestLocation " )
51
95
public class func promise( _ requestAuthorizationType: RequestAuthorizationType = . automatic, satisfying block: ( ( CLLocation ) -> Bool ) ? = nil ) -> Promise < [ CLLocation ] > {
52
96
return requestLocation ( authorizationType: requestAuthorizationType, satisfying: block)
53
97
}
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
- }
65
98
}
66
99
67
100
private class LocationManager : CLLocationManager , CLLocationManagerDelegate {
@@ -71,16 +104,30 @@ private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
71
104
@objc fileprivate func locationManager( _ manager: CLLocationManager , didUpdateLocations locations: [ CLLocation ] ) {
72
105
if let block = satisfyingBlock {
73
106
let satisfiedLocations = locations. filter ( block)
74
- if satisfiedLocations. count > 0 {
107
+ if ! satisfiedLocations. isEmpty {
75
108
seal. fulfill ( satisfiedLocations)
109
+ } else {
110
+ #if os(tvOS)
111
+ requestLocation ( )
112
+ #endif
76
113
}
77
114
} else {
78
115
seal. fulfill ( locations)
79
116
}
80
117
}
81
118
82
119
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
+ }
84
131
}
85
132
86
133
@objc func locationManager( _ manager: CLLocationManager , didFailWithError error: Error ) {
@@ -94,39 +141,120 @@ private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
94
141
}
95
142
96
143
97
- #if os(iOS) || os(watchOS )
144
+ #if ! os(macOS )
98
145
99
146
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
+ }
105
202
}
106
203
}
107
204
205
+ @available ( iOS 8 , * )
108
206
private class AuthorizationCatcher : CLLocationManager , CLLocationManagerDelegate {
109
207
let ( promise, fulfill) = Guarantee< CLAuthorizationStatus> . pending( )
110
208
var retainCycle : AuthorizationCatcher ?
111
209
let initialAuthorizationState = CLLocationManager . authorizationStatus ( )
112
210
113
- init ( auther : ( CLLocationManager ) -> Void , type: CLLocationManager . RequestAuthorizationType ) {
211
+ init ( type: PMKCLAuthorizationType ) {
114
212
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 ) {
121
215
delegate = self
122
- auther ( self )
123
216
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
+ }
126
241
}
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
+ }
129
256
}
257
+ #endif
130
258
}
131
259
132
260
@objc fileprivate func locationManager( _ manager: CLLocationManager , didChangeAuthorization status: CLAuthorizationStatus ) {
@@ -137,42 +265,43 @@ private class AuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate
137
265
}
138
266
}
139
267
140
- private func auther ( _ requestAuthorizationType : CLLocationManager . RequestAuthorizationType ) -> ( ( CLLocationManager ) -> Void ) {
268
+ #endif
141
269
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 {
144
278
func hasInfoPlistKey( _ key: String ) -> Bool {
145
- let value = Bundle . main . object ( forInfoDictionaryKey: key) as? String ?? " "
279
+ let value = object ( forInfoDictionaryKey: key) as? String ?? " "
146
280
return !value. isEmpty
147
281
}
148
282
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
+ }
165
292
293
+ if #available( iOS 11 , * ) {
294
+ NSLog ( " PromiseKit: warning: `NSLocationAlwaysAndWhenInUseUsageDescription` key not set " )
295
+ } else {
296
+ NSLog ( " PromiseKit: warning: `NSLocationWhenInUseUsageDescription` key not set " )
166
297
}
298
+
299
+ // won't work, but we warned the user above at least
300
+ return . whenInUse
167
301
}
168
302
}
169
303
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
174
307
}
175
-
176
- #endif
177
-
178
- #endif
0 commit comments