-
Notifications
You must be signed in to change notification settings - Fork 6
/
ICanHas.swift
271 lines (232 loc) · 11.8 KB
/
ICanHas.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
import Foundation
import UIKit
import CoreLocation
import AVFoundation
import Photos
import AddressBookUI
import EventKit
public typealias AuthClosure = (_ authorized: Bool) -> Void
public typealias AuthErrorClosure = (_ authorized: Bool, _ error: Error?) -> Void
public typealias AuthStatusClosure<Status> = (_ authorized: Bool, _ status: Status) -> Void
public typealias AuthStatusErrorClosure<Status> = (_ authorized: Bool, _ status: Status, _ error: Error?) -> Void
private typealias _AuthClosure = (Bool) -> Void
private typealias _AuthErrorClosure = ((Bool, Error?)) -> Void
private typealias _AuthStatusClosure<Status> = ((Bool, Status)) -> Void
private typealias _AuthStatusErrorClosure<Status> = ((Bool, Status, Error?)) -> Void
private func complete<P>(_ closures: inout [(P) -> Void], _ flag: inout Bool, _ params: P) {
let array = closures
closures = []
for closure in array { closure(params) }
flag = true
}
private func observeOnce(notificationName: Notification.Name, queue: OperationQueue = .main, using closure: @escaping (Notification) -> Void) {
var observerObject: NSObjectProtocol?
observerObject = NotificationCenter.default.addObserver(forName: notificationName, object: nil, queue: queue) {
note in
guard let object = observerObject else { return }
observerObject = nil
NotificationCenter.default.removeObserver(object)
closure(note)
}
}
open class ICanHas {
fileprivate class func onMain(_ closure: @escaping () -> Void) { DispatchQueue.main.async(execute: closure) }
static private var didTryToRegisterForPush = false
static private var isHasingPush = false
static private var isHasingLocation = false
static private var isHasingCapture: [AVMediaType: Bool] = [:]
static private var isHasingPhotos = false
static private var isHasingContacts = false
static private var isHasingCalendar: [EKEntityType: Bool] = [:]
static private var hasPushClosures: [_AuthClosure] = []
static private var hasLocationClosures: [_AuthStatusClosure<CLAuthorizationStatus>] = []
static private var hasCaptureClosures: [AVMediaType: [_AuthStatusClosure<AVAuthorizationStatus>]] = [:]
static private var hasPhotosClosures: [_AuthStatusClosure<PHAuthorizationStatus>] = []
static private var hasContactsClosures: [_AuthStatusErrorClosure<ABAuthorizationStatus>] = []
static private var hasCalendarClosures: [EKEntityType: [_AuthStatusErrorClosure<EKAuthorizationStatus>]] = [:]
open class func calendarAuthorizationStatus(for type: EKEntityType = EKEntityType.event) -> EKAuthorizationStatus {
return EKEventStore.authorizationStatus(for: type)
}
open class func calendarAuthorization(for type: EKEntityType = EKEntityType.event) -> Bool {
return calendarAuthorizationStatus(for: type) == .authorized
}
open class func calendar(store: EKEventStore = EKEventStore(), type: EKEntityType = .event, closure: @escaping AuthStatusErrorClosure<EKAuthorizationStatus>) {
onMain {
hasCalendarClosures[type, default: []].append(closure)
guard !isHasingCalendar[type, default: false] else { return }
isHasingCalendar[type] = true
let done: AuthStatusErrorClosure<EKAuthorizationStatus> = { authorized, status, error in
complete(&hasCalendarClosures[type, default: []], &isHasingCalendar[type, default: false], (authorized, status, error))
}
let status = calendarAuthorizationStatus(for: type)
switch status {
case .denied, .restricted: done(false, status, nil)
case .authorized: done(true, status, nil)
case .notDetermined:
store.requestAccess(to: type) { authorized, error in
onMain { done(authorized, calendarAuthorizationStatus(for: type), error) }
}
}
}
}
open class func contactsAuthorizationStatus() -> ABAuthorizationStatus {
return ABAddressBookGetAuthorizationStatus()
}
open class func contactsAuthorization() -> Bool {
return contactsAuthorizationStatus() == .authorized
}
open class func contacts(book: ABAddressBook? = ABAddressBookCreateWithOptions(nil, nil)?.takeRetainedValue(), closure: @escaping AuthStatusErrorClosure<ABAuthorizationStatus>) {
onMain {
hasContactsClosures.append(closure)
guard !isHasingContacts else { return }
isHasingContacts = true
let done: AuthStatusErrorClosure<ABAuthorizationStatus> = { authorized, status, error in
complete(&hasContactsClosures, &isHasingContacts, (authorized, status, error))
}
let status = contactsAuthorizationStatus()
switch status {
case .denied, .restricted: done(false, status, nil)
case .authorized: done(true, status, nil)
case .notDetermined:
ABAddressBookRequestAccessWithCompletion(book) { authorized, error in
onMain { done(authorized, contactsAuthorizationStatus(), error) }
}
}
}
}
open class func photosAuthorizationStatus() -> PHAuthorizationStatus {
return PHPhotoLibrary.authorizationStatus()
}
open class func photosAuthorization() -> Bool {
return photosAuthorizationStatus() == .authorized
}
open class func photos(closure: @escaping AuthStatusClosure<PHAuthorizationStatus>) {
onMain {
hasPhotosClosures.append(closure)
guard !isHasingPhotos else { return }
isHasingPhotos = true
let done: AuthStatusClosure<PHAuthorizationStatus> = { authorized, status in
complete(&hasPhotosClosures, &isHasingPhotos, (authorized, status))
}
let status = photosAuthorizationStatus()
switch status {
case .denied, .restricted: done(false, status)
case .authorized: done(true, status)
case .notDetermined:
PHPhotoLibrary.requestAuthorization { status in
onMain { done(status == .authorized, status) }
}
}
}
}
open class func captureAuthorizationStatus(for type: AVMediaType = .video) -> AVAuthorizationStatus {
return AVCaptureDevice.authorizationStatus(for: type)
}
open class func captureAuthorization(for type: AVMediaType = .video) -> Bool {
return captureAuthorizationStatus(for: type) == .authorized
}
open class func capture(type: AVMediaType = .video, closure: @escaping AuthStatusClosure<AVAuthorizationStatus>) {
onMain {
hasCaptureClosures[type, default: []].append(closure)
guard !isHasingCapture[type, default: false] else { return }
isHasingCapture[type] = true
let done: AuthStatusClosure<AVAuthorizationStatus> = { authorized, status in
complete(&hasCaptureClosures[type, default: []], &isHasingCapture[type, default:false], (authorized, status))
}
let status = captureAuthorizationStatus(for: type)
switch status {
case .denied, .restricted: done(false, status)
case .authorized: done(true, status)
case .notDetermined:
AVCaptureDevice.requestAccess(for: type) { authorized in
onMain { done(authorized, captureAuthorizationStatus(for: type)) }
}
}
}
}
open class func pushAuthorization() -> Bool {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
open class func push(types: UIUserNotificationType = [.alert, .badge, .sound], closure: @escaping AuthClosure) {
onMain {
hasPushClosures.append(closure)
guard !isHasingPush else { return }
isHasingPush = true
let done: AuthClosure = { authorized in
complete(&hasPushClosures, &isHasingPush, authorized)
}
let application = UIApplication.shared
guard !didTryToRegisterForPush else {
done(application.isRegisteredForRemoteNotifications)
return
}
didTryToRegisterForPush = true
application.registerUserNotificationSettings(UIUserNotificationSettings(types: types, categories: nil))
var hasTimedOut = false
var hasGoneToBackground = false
var waitingForForeground = false
observeOnce(notificationName: .UIApplicationWillResignActive) { _ in
hasGoneToBackground = true
waitingForForeground = !hasTimedOut || waitingForForeground
}
observeOnce(notificationName: .UIApplicationDidBecomeActive) { _ in
guard waitingForForeground else { return }
done(application.isRegisteredForRemoteNotifications)
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
hasTimedOut = true
guard !hasGoneToBackground else { return }
done(application.isRegisteredForRemoteNotifications)
}
application.registerForRemoteNotifications()
}
}
open class func locationAuthorizationStatus() -> CLAuthorizationStatus {
return CLLocationManager.authorizationStatus()
}
open class func locationAuthorization(background: Bool = false) -> Bool {
let status = locationAuthorizationStatus()
return status == .authorizedAlways || (!background && status == .authorizedWhenInUse)
}
open class func location(background: Bool = false, manager defaultManager: CLLocationManager? = nil, closure: @escaping AuthStatusClosure<CLAuthorizationStatus>) {
onMain {
hasLocationClosures.append(closure)
guard !isHasingLocation else { return }
ICanHas.isHasingLocation = true
let done: AuthStatusClosure<CLAuthorizationStatus> = { authorized, status in
complete(&hasLocationClosures, &isHasingLocation, (authorized, status))
}
let status = locationAuthorizationStatus()
switch status {
case .authorizedAlways: done(true, status)
case .denied, .restricted: done(false, status)
case .authorizedWhenInUse:
guard !background else { fallthrough }
done(true, status)
case .notDetermined:
var manager: CLLocationManager? = defaultManager ?? CLLocationManager()
var completed = false
var hasTimedOut = false
var canTimeOut = true
let complete: (Bool) -> Void = {
worked in
guard !completed else { return }
completed = true
manager = nil
done(worked && locationAuthorization(background: background), locationAuthorizationStatus())
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
guard canTimeOut else { return }
hasTimedOut = true
complete(false)
}
observeOnce(notificationName: .UIApplicationWillResignActive) { _ in canTimeOut = false }
observeOnce(notificationName: .UIApplicationDidBecomeActive) { _ in
guard !hasTimedOut else { return }
complete(true)
}
manager?.requestWhenInUseAuthorization()
}
}
}
}