2 |
3 |
14 | Usage • Example • Installation • License 15 |
16 | 17 | ## Usage 18 | 19 | #### Permission 20 | 21 | > [`Permission.swift`](https://github.com/delba/Permission/blob/master/Source/Permission.swift) 22 | > [`PermissionStatus.swift`](https://github.com/delba/Permission/blob/master/Source/PermissionStatus.swift) 23 | 24 | ```swift 25 | let permission: Permission = .contacts 26 | 27 | print(permission.status) // .notDetermined 28 | 29 | permission.request { status in 30 | switch status { 31 | case .authorized: print("authorized") 32 | case .denied: print("denied") 33 | case .disabled: print("disabled") 34 | case .notDetermined: print("not determined") 35 | } 36 | } 37 | ``` 38 | 39 | ##### Supported Permissions 40 | 41 | > [`PermissionType.swift`](https://github.com/delba/Permission/blob/master/Source/PermissionType.swift) 42 | > [`Types/`](https://github.com/delba/Permission/tree/master/Source/Types) 43 | 44 | - [`Bluetooth`](https://github.com/delba/Permission/blob/master/Source/Types/Bluetooth.swift) 45 | - [`Camera`](https://github.com/delba/Permission/blob/master/Source/Types/Camera.swift) 46 | - [`Contacts`](https://github.com/delba/Permission/blob/master/Source/Types/Contacts.swift) 47 | - [`Events`](https://github.com/delba/Permission/blob/master/Source/Types/Events.swift) 48 | - [`Motion`](https://github.com/delba/Permission/blob/master/Source/Types/Motion.swift) 49 | - [`Microphone`](https://github.com/delba/Permission/blob/master/Source/Types/Microphone.swift) 50 | - [`Notifications`](https://github.com/delba/Permission/blob/master/Source/Types/Notifications.swift) 51 | - [`Photos`](https://github.com/delba/Permission/blob/master/Source/Types/Photos.swift) 52 | - [`Reminders`](https://github.com/delba/Permission/blob/master/Source/Types/Reminders.swift) 53 | - [`LocationAlways`](https://github.com/delba/Permission/blob/master/Source/Types/LocationAlways.swift) 54 | - [`LocationWhenInUse`](https://github.com/delba/Permission/blob/master/Source/Types/LocationWhenInUse.swift) 55 | - [`MediaLibrary`](https://github.com/delba/Permission/blob/master/Source/Types/MediaLibrary.swift) 56 | - [`SpeechRecognizer`](https://github.com/delba/Permission/blob/master/Source/Types/SpeechRecognizer.swift) 57 | - [`Siri`](https://github.com/delba/Permission/blob/master/Source/Types/Siri.swift) 58 | 59 | #### PermissionAlert 60 | 61 | > [`PermissionAlert.swift`](https://github.com/delba/Permission/blob/master/Source/PermissionAlert.swift) 62 | 63 | ##### Denied and disabled alerts 64 | 65 | When you first request a permission, a system alert is presented to the user. 66 | If you request a permission that was denied/disabled, a `PermissionAlert` will be presented. 67 | You might want to change the default `title`, `message`, `cancel` and `settings` text: 68 | 69 | ```swift 70 | let alert = permission.deniedAlert // or permission.disabledAlert 71 | 72 | alert.title = "Please allow access to your contacts" 73 | alert.message = nil 74 | alert.cancel = "Cancel" 75 | alert.settings = "Settings" 76 | ``` 77 | 78 | Set `permission.presentDeniedAlert = false` or `permission.presentDisabledAlert = false` if you don't want to present these alerts. 79 | 80 | ##### Pre-permission alerts 81 | 82 | In order not to burn your only chance of displaying the system alert, you can present a **pre-permission alert**. See this [article](http://techcrunch.com/2014/04/04/the-right-way-to-ask-users-for-ios-permissions/) for more informations. 83 | 84 | ```swift 85 | permission.presentPrePermissionAlert = true 86 | 87 | let alert = permission.prePermissionAlert 88 | 89 | alert.title = "Let Foo Access Photos?" 90 | alert.message = "This lets you choose which photos you want to add to your Foo profile" 91 | alert.cancel = "Not now" 92 | alert.confirm = "Give Access" 93 | ``` 94 | 95 | The system alert will only be presented if the user taps "Give Access". 96 | 97 | #### PermissionSet 98 | 99 | > [`PermissionSet.swift`](https://github.com/delba/Permission/blob/master/Source/PermissionSet.swift) 100 | 101 | Use a `PermissionSet` to check the status of a group of `Permission` and to react when a permission is requested. 102 | 103 | ```swift 104 | let permissionSet = PermissionSet(.contacts, .camera, .microphone, .photos) 105 | permissionSet.delegate = self 106 | 107 | print(permissionSet.status) // .notDetermined 108 | 109 | // ... 110 | 111 | func permissionSet(permissionSet: PermissionSet, willRequestPermission permission: Permission) { 112 | print("Will request \(permission)") 113 | } 114 | 115 | func permissionSet(permissionSet: PermissionSet, didRequestPermission permission: Permission) { 116 | switch permissionSet.status { 117 | case .authorized: print("all the permissions are granted") 118 | case .denied: print("at least one permission is denied") 119 | case .disabled: print("at least one permission is disabled") 120 | case .notDetermined: print("at least one permission is not determined") 121 | } 122 | } 123 | ``` 124 | 125 | #### PermissionButton 126 | 127 | > [`PermissionButton`](https://github.com/delba/Permission/blob/master/Source/PermissionButton.swift) 128 | 129 | A `PermissionButton` requests the permission when tapped and updates itself when its underlying permission status changes. 130 | 131 | ```swift 132 | let button = PermissionButton(.photos) 133 | ``` 134 | 135 | `PermissionButton` is a subclass of `UIButton`. All the getters and setters of `UIButton` have their equivalent in `PermissionButton`. 136 | 137 | ```swift 138 | button.setTitles([ 139 | .authorized: "Authorized", 140 | .denied: "Denied", 141 | .disabled: "Disabled", 142 | .notDetermined: "Not determined" 143 | ]) 144 | 145 | // button.setAttributedTitles 146 | // button.setTitleColors 147 | // button.setTitleShadowColors 148 | // button.setImages 149 | // button.setBackgroundImages 150 | // etc. 151 | ``` 152 | 153 | #### Third-party libraries: 154 | 155 | - [sunshinejr/**RxPermission**](https://github.com/sunshinejr/RxPermission) RxSwift bindings for Permissions API in iOS. 156 | 157 | ## Example 158 | 159 | ```swift 160 | class PermissionsViewController: UIViewController, PermissionSetDelegate { 161 | 162 | override func viewDidLoad() { 163 | super.viewDidLoad() 164 | 165 | let label = UILabel() 166 | 167 | let contacts = PermissionButton(.contacts) 168 | let camera = PermissionButton(.camera) 169 | let microphone = PermissionButton(.microphone) 170 | let photos = PermissionButton(.photos) 171 | 172 | contacts.setTitles([ 173 | .notDetermined: "Contacts - NotDetermined" 174 | .authorized: "Contacts - Authorized", 175 | .denied: "Contacts - Denied" 176 | ]) 177 | 178 | contacts.setTitleColors([ 179 | .notDetermined: .black, 180 | .authorized: .green, 181 | .denied: .red 182 | ]) 183 | 184 | // ... 185 | 186 | let permissionSet = PermissionSet(contacts, camera, microphone, photos) 187 | 188 | permissionSet.delegate = self 189 | 190 | label.text = String(describing: permissionSet.status) 191 | 192 | for subview in [label, contacts, camera, microphone, photos] { 193 | view.addSubview(subview) 194 | } 195 | } 196 | 197 | func permissionSet(permissionSet: PermissionSet, didRequestPermission permission: Permission) { 198 | label.text = String(permissionSet.status) 199 | } 200 | } 201 | ``` 202 | 203 |
204 |
205 | ## Installation
206 |
207 | ### Carthage
208 |
209 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.
210 |
211 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
212 |
213 | ```bash
214 | $ brew update
215 | $ brew install carthage
216 | ```
217 |
218 | To integrate Permission into your Xcode project using Carthage, specify it in your `Cartfile`:
219 |
220 | ```ogdl
221 | github "delba/Permission"
222 | ```
223 |
224 | ##### Configuration
225 |
226 | Due to Apple's new policy regarding permission access, binaries may be rejected due to a perceived attempt
227 | to access privacy-sensitive data without a usage key, and then further rejected for not actually requesting
228 | permissions.
229 |
230 | As a workaround, you can provide custom build flags _before_ building the dynamic framework to only compile
231 | with permissions you request. This is done by adding a configuration file named `PermissionConfiguration.xcconfig`
232 | to the root of your project. For convenience, you can use
233 | `PermissionConfiguration.xcconfig` in the `Permission/` repo directory. Just comment out the permissions
234 | you want to use, and compile the framework.
235 |
236 | To compile with only notifications and photos permissions:
237 | ```
238 | PERMISSION_BLUETOOTH = // PERMISSION_BLUETOOTH
239 | PERMISSION_CAMERA = PERMISSION_CAMERA
240 | PERMISSION_CONTACTS = // PERMISSION_CONTACTS
241 | PERMISSION_EVENTS = // PERMISSION_EVENTS
242 | PERMISSION_LOCATION = // PERMISSION_LOCATION
243 | PERMISSION_MICROPHONE = // PERMISSION_MICROPHONE
244 | PERMISSION_MOTION = // PERMISSION_MOTION
245 | PERMISSION_NOTIFICATIONS = PERMISSION_NOTIFICATIONS
246 | PERMISSION_PHOTOS = // PERMISSION_PHOTOS
247 | PERMISSION_REMINDERS = // PERMISSION_REMINDERS
248 | PERMISSION_SPEECH_RECOGNIZER = // PERMISSION_SPEECH_RECOGNIZER
249 | PERMISSION_MEDIA_LIBRARY = // PERMISSION_MEDIA_LIBRARY
250 |
251 | // Do not modify this line. Instead, remove comments above as needed to enable the categories your app uses.
252 | PERMISSION_FLAGS= $(PERMISSION_BLUETOOTH) $(PERMISSION_CAMERA) $(PERMISSION_CONTACTS) $(PERMISSION_EVENTS) $(PERMISSION_LOCATION) $(PERMISSION_MICROPHONE) $(PERMISSION_MOTION) $(PERMISSION_NOTIFICATIONS) $(PERMISSION_PHOTOS) $(PERMISSION_REMINDERS) $(PERMISSION_SPEECH_RECOGNIZER) $(PERMISSION_MEDIA_LIBRARY)
253 |
254 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) $(PERMISSION_FLAGS)
255 | ```
256 |
257 | ### Cocoapods
258 |
259 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects.
260 |
261 | You can install it with the following command:
262 |
263 | ```bash
264 | $ gem install cocoapods
265 | ```
266 |
267 | To integrate Permission into your Xcode project using CocoaPods, specify it in your `Podfile`. Due to Apple's new policy regarding permission access you need to specifically define what kind of permissions you want to access using subspecs. For example if you want to access the Camera and the Notifications you define the following:
268 |
269 | ```ruby
270 | use_frameworks!
271 |
272 | pod 'Permission/Camera'
273 | pod 'Permission/Notifications'
274 | ```
275 |
276 | Please see `Permission.podspec` for more information about which subspecs are available.
277 |
278 | ## License
279 |
280 | Copyright (c) 2015-2019 Damien (http://delba.io)
281 |
282 | Permission is hereby granted, free of charge, to any person obtaining a copy
283 | of this software and associated documentation files (the "Software"), to deal
284 | in the Software without restriction, including without limitation the rights
285 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
286 | copies of the Software, and to permit persons to whom the Software is
287 | furnished to do so, subject to the following conditions:
288 |
289 | The above copyright notice and this permission notice shall be included in all
290 | copies or substantial portions of the Software.
291 |
292 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
293 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
294 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
295 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
296 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
297 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
298 | SOFTWARE.
299 |
--------------------------------------------------------------------------------
/Source/PermissionButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PermissionButton.swift
3 | //
4 | // Copyright (c) 2015-2019 Damien (http://delba.io)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 | //
24 |
25 | open class PermissionButton: UIButton {
26 |
27 | /// The permission of the button.
28 | public let permission: Permission
29 |
30 | /// The permission type of the button.
31 | open var type: PermissionType { return permission.type }
32 |
33 | /// The permission status of the button.
34 | open var status: PermissionStatus { return permission.status }
35 |
36 | private var titles: [UIControl.State: [PermissionStatus: String]] = [:]
37 | private var attributedTitles: [UIControl.State: [PermissionStatus: NSAttributedString]] = [:]
38 | private var titleColors: [UIControl.State: [PermissionStatus: UIColor]] = [:]
39 | private var titleShadowColors: [UIControl.State: [PermissionStatus: UIColor]] = [:]
40 | private var images: [UIControl.State: [PermissionStatus: UIImage]] = [:]
41 | private var backgroundImages: [UIControl.State: [PermissionStatus: UIImage]] = [:]
42 |
43 | /// The alert when the permission was denied.
44 | open var deniedAlert: PermissionAlert {
45 | return permission.deniedAlert
46 | }
47 |
48 | /// The alert when the permission is disabled.
49 | open var disabledAlert: PermissionAlert {
50 | return permission.disabledAlert
51 | }
52 |
53 | /// The textual representation of self.
54 | open override var description: String {
55 | return permission.description
56 | }
57 |
58 | // MARK: - Initialization
59 |
60 | /**
61 | Creates and returns a new button for the specified permission.
62 |
63 | - parameter permission: The permission.
64 |
65 | - returns: A newly created button.
66 | */
67 | public init(_ permission: Permission) {
68 | self.permission = permission
69 |
70 | super.init(frame: .zero)
71 |
72 | addTarget(self, action: .tapped, for: .touchUpInside)
73 | addTarget(self, action: .highlight, for: .touchDown)
74 | }
75 |
76 | /**
77 | Returns an object initialized from data in a given unarchiver.
78 |
79 | - parameter aDecoder: An unarchiver object.
80 |
81 | - returns: self, initialized using the data in decoder.
82 | */
83 | public required init?(coder aDecoder: NSCoder) {
84 | fatalError("init(coder:) has not been implemented")
85 | }
86 |
87 | // MARK: - Titles
88 |
89 | /**
90 | Returns the title associated with the specified permission status and state.
91 |
92 | - parameter status: The permission status that uses the title.
93 | - parameter state: The state that uses the title.
94 |
95 | - returns: The title for the specified permission status and state.
96 | */
97 | open func titleForStatus(_ status: PermissionStatus, andState state: UIControl.State = .normal) -> String? {
98 | return titles[state]?[status]
99 | }
100 |
101 | /**
102 | Sets the title to use for the specified state.
103 |
104 | - parameter title: The title to use for the specified state.
105 | - parameter state: The state that uses the specified title.
106 | */
107 | open override func setTitle(_ title: String?, for state: UIControl.State) {
108 | titles[state] = nil
109 | super.setTitle(title, for: state)
110 | }
111 |
112 | /**
113 | Sets the title to use for the specified permission status and state.
114 |
115 | - parameter title: The title to use for the specified state.
116 | - parameter status: The permission status that uses the specified title.
117 | - parameter state: The state that uses the specified title.
118 | */
119 | open func setTitle(_ title: String?, forStatus status: PermissionStatus, andState state: UIControl.State = .normal) {
120 | guard [.normal, .highlighted].contains(state) else { return }
121 |
122 | if titles[state] == nil {
123 | titles[state] = [:]
124 | }
125 |
126 | titles[state]?[status] = title
127 | }
128 |
129 | /**
130 | Sets the titles to use for the specified permission statuses and state.
131 |
132 | - parameter titles: The titles to use for the specified statuses.
133 | - parameter state: The state that uses the specifed titles.
134 | */
135 | open func setTitles(_ titles: [PermissionStatus: String?], forState state: UIControl.State = .normal) {
136 | guard [.normal, .highlighted].contains(state) else { return }
137 |
138 | if self.titles[state] == nil {
139 | self.titles[state] = [:]
140 | }
141 |
142 | for (status, title) in titles {
143 | self.titles[state]?[status] = title
144 | }
145 | }
146 |
147 | // MARK: - Attributed titles
148 |
149 | /**
150 | Returns the styled title associated with the specified permission status and state.
151 |
152 | - parameter status: The permission status that uses the styled title.
153 | - parameter state: The state that uses the styled title.
154 |
155 | - returns: The title for the specified permission status and state.
156 | */
157 | open func attributedTitleForStatus(_ status: PermissionStatus, andState state: UIControl.State = .normal) -> NSAttributedString? {
158 | return attributedTitles[state]?[status]
159 | }
160 |
161 | /**
162 | Sets the styled title to use for the specified state.
163 |
164 | - parameter title: The styled text string to use for the title.
165 | - parameter state: The state that uses the specified title.
166 | */
167 | open override func setAttributedTitle(_ title: NSAttributedString?, for state: UIControl.State) {
168 | attributedTitles[state] = nil
169 | super.setAttributedTitle(title, for: state)
170 | }
171 |
172 | /**
173 | Sets the styled title to use for the specifed permission status and state.
174 |
175 | - parameter title: The styled text string to use for the title.
176 | - parameter status: The permission status that uses the specified title.
177 | - parameter state: The state that uses the specified title.
178 | */
179 | open func setAttributedTitle(_ title: NSAttributedString?, forStatus status: PermissionStatus, andState state: UIControl.State = .normal) {
180 | guard [.normal, .highlighted].contains(state) else { return }
181 |
182 | if attributedTitles[state] == nil {
183 | attributedTitles[state] = [:]
184 | }
185 |
186 | attributedTitles[state]?[status] = title
187 | }
188 |
189 | /**
190 | Sets the styled titles to use for the specified permission statuses and state.
191 |
192 | - parameter titles: The titles to use for the specified statuses.
193 | - parameter state: The state that uses the specified titles.
194 | */
195 | open func setAttributedTitles(_ titles: [PermissionStatus: NSAttributedString?], forState state: UIControl.State = .normal) {
196 | guard [.normal, .highlighted].contains(state) else { return }
197 |
198 | if attributedTitles[state] == nil {
199 | attributedTitles[state] = [:]
200 | }
201 |
202 | for (status, title) in titles {
203 | attributedTitles[state]?[status] = title
204 | }
205 | }
206 |
207 | // MARK: - Title colors
208 |
209 | /**
210 | Returns the title color used for a permission status and state.
211 |
212 | - parameter status: The permission status that uses the title color.
213 | - parameter state: The state that uses the title color.
214 |
215 | - returns: The color of the title for the specified permission status and state.
216 | */
217 | open func titleColorForStatus(_ status: PermissionStatus, andState state: UIControl.State = .normal) -> UIColor? {
218 | return titleColors[state]?[status]
219 | }
220 |
221 | /**
222 | Sets the color of the title to use for the specified state.
223 |
224 | - parameter color: The color of the title to use for the specified state.
225 | - parameter state: The state that uses the specified color.
226 | */
227 | open override func setTitleColor(_ color: UIColor?, for state: UIControl.State) {
228 | titleColors[state] = nil
229 | super.setTitleColor(color, for: state)
230 | }
231 |
232 | /**
233 | Sets the color of the title to use for the specified permission status and state.
234 |
235 | - parameter color: The color of the title to use for the specified permission status and state.
236 | - parameter status: The permission status that uses the specified color.
237 | - parameter state: The state that uses the specified color.
238 | */
239 | open func setTitleColor(_ color: UIColor?, forStatus status: PermissionStatus, andState state: UIControl.State = .normal) {
240 | guard [.normal, .highlighted].contains(state) else { return }
241 |
242 | if titleColors[state] == nil {
243 | titleColors[state] = [:]
244 | }
245 |
246 | titleColors[state]?[status] = color
247 | }
248 |
249 | /**
250 | Sets the colors of the title to use for the specified permission statuses and state.
251 |
252 | - parameter colors: The colors to use for the specified permission statuses.
253 | - parameter state: The state that uses the specified colors.
254 | */
255 | open func setTitleColors(_ colors: [PermissionStatus: UIColor?], forState state: UIControl.State = .normal) {
256 | guard [.normal, .highlighted].contains(state) else { return }
257 |
258 | if titleColors[state] == nil {
259 | titleColors[state] = [:]
260 | }
261 |
262 | for (status, color) in colors {
263 | titleColors[state]?[status] = color
264 | }
265 | }
266 |
267 | // MARK: - Title shadow colors
268 |
269 | /**
270 | Returns the shadow color of the title used for a permission status and state.
271 |
272 | - parameter status: The permission status that uses the title shadow color.
273 | - parameter state: The state that uses the title shadow color.
274 |
275 | - returns: The color of the title's shadow for the specified permission status and state.
276 | */
277 | open func titleShadowColorForStatus(_ status: PermissionStatus, andState state: UIControl.State = .normal) -> UIColor? {
278 | return titleShadowColors[state]?[status]
279 | }
280 |
281 | /**
282 | Sets the color of the title shadow to use for the specified state.
283 |
284 | - parameter color: The color of the title shadow to use for the specified state.
285 | - parameter state: The state that uses the specified color.
286 | */
287 | open override func setTitleShadowColor(_ color: UIColor?, for state: UIControl.State) {
288 | titleShadowColors[state] = nil
289 | super.setTitleShadowColor(color, for: state)
290 | }
291 |
292 | /**
293 | Sets the color of the title shadow to use for the specified permission status and state.
294 |
295 | - parameter color: The color of the title shadow to use for the specified permission status and state.
296 | - parameter status: The permission status that uses the specified color.
297 | - parameter state: The state that uses the specified color.
298 | */
299 | open func setTitleShadowColor(_ color: UIColor?, forStatus status: PermissionStatus, andState state: UIControl.State = .normal) {
300 | guard [.normal, .highlighted].contains(state) else { return }
301 |
302 | if titleShadowColors[state] == nil {
303 | titleShadowColors[state] = [:]
304 | }
305 |
306 | titleShadowColors[state]?[status] = color
307 | }
308 |
309 | /**
310 | Sets the colors of the title shadow to use for the specified permission statuses and state.
311 |
312 | - parameter colors: The colors to use for the specified permission statuses.
313 | - parameter state: The state that uses the specified colors.
314 | */
315 | open func setTitleShadowColors(_ colors: [PermissionStatus: UIColor?], forState state: UIControl.State = .normal) {
316 | guard [.normal, .highlighted].contains(state) else { return }
317 |
318 | if titleShadowColors[state] == nil {
319 | titleShadowColors[state] = [:]
320 | }
321 |
322 | for (status, color) in colors {
323 | titleShadowColors[state]?[status] = color
324 | }
325 | }
326 |
327 | // MARK: - Images
328 |
329 | /**
330 | Returns the image used for a permission status and state
331 |
332 | - parameter status: The permission status that uses the image.
333 | - parameter state: The state that uses the image.
334 |
335 | - returns: The image used for the specified permission status and state.
336 | */
337 | open func imageForStatus(_ status: PermissionStatus, andState state: UIControl.State = .normal) -> UIImage? {
338 | return images[state]?[status]
339 | }
340 |
341 | /**
342 | Sets the image to use for the specified state.
343 |
344 | - parameter image: The image to use for the specified state.
345 | - parameter state: The state that uses the specified image.
346 | */
347 | open override func setImage(_ image: UIImage?, for state: UIControl.State) {
348 | images[state] = nil
349 | super.setImage(image, for: state)
350 | }
351 |
352 | /**
353 | Sets the image to use for the specified permission status and state.
354 |
355 | - parameter image: The image to use for the specified permission status and state.
356 | - parameter status: The permission status that uses the specified image.
357 | - parameter state: The state that uses the specified image.
358 | */
359 | open func setImage(_ image: UIImage?, forStatus status: PermissionStatus, andState state: UIControl.State = .normal) {
360 | guard [.normal, .highlighted].contains(state) else { return }
361 |
362 | if images[state] == nil {
363 | images[state] = [:]
364 | }
365 |
366 | images[state]?[status] = image
367 | }
368 |
369 | /**
370 | Sets the images for the specified permission statuses and state.
371 |
372 | - parameter images: The images to use for the specified permission statuses.
373 | - parameter state: The state that uses the specified images.
374 | */
375 | open func setImages(_ images: [PermissionStatus: UIImage], forState state: UIControl.State = .normal) {
376 | guard [.normal, .highlighted].contains(state) else { return }
377 |
378 | if self.images[state] == nil {
379 | self.images[state] = [:]
380 | }
381 |
382 | for (status, image) in images {
383 | self.images[state]?[status] = image
384 | }
385 | }
386 |
387 | // MARK: - Background images
388 |
389 | /**
390 | Returns the background image used for a permission status and a button state.
391 |
392 | - parameter status: The permission status that uses the background image.
393 | - parameter state: The state that uses the background image.
394 |
395 | - returns: The background image used for the specified permission status and state.
396 | */
397 | open func backgroundImageForStatus(_ status: PermissionStatus, andState state: UIControl.State = .normal) -> UIImage? {
398 | return backgroundImages[state]?[status]
399 | }
400 |
401 | /**
402 | Sets the background image to use for the specified button state.
403 |
404 | - parameter image: The background image to use for the specified state.
405 | - parameter state: The state that uses the specified image.
406 | */
407 | open override func setBackgroundImage(_ image: UIImage?, for state: UIControl.State) {
408 | backgroundImages[state] = nil
409 | super.setBackgroundImage(image, for: state)
410 | }
411 |
412 | /**
413 | Sets the background image to use for the specified permission status and button state.
414 |
415 | - parameter image: The background image to use for the specified permission status and button state.
416 | - parameter status: The permission status that uses the specified image.
417 | - parameter state: The state that uses the specified image.
418 | */
419 | open func setBackgroundImage(_ image: UIImage?, forStatus status: PermissionStatus, andState state: UIControl.State = .normal) {
420 | guard [.normal, .highlighted].contains(state) else { return }
421 |
422 | if backgroundImages[state] == nil {
423 | backgroundImages[state] = [:]
424 | }
425 |
426 | backgroundImages[state]?[status] = image
427 | }
428 |
429 | /**
430 | Set the background images to use for the specified permission statuses and button state.
431 |
432 | - parameter images: The background images to use for the specified permission statuses.
433 | - parameter state: The state that uses the specified images.
434 | */
435 | open func setBackgroundImages(_ images: [PermissionStatus: UIImage], forState state: UIControl.State = .normal) {
436 | guard [.normal, .highlighted].contains(state) else { return }
437 |
438 | if backgroundImages[state] == nil {
439 | backgroundImages[state] = [:]
440 | }
441 |
442 | for (status, image) in images {
443 | backgroundImages[state]?[status] = image
444 | }
445 | }
446 |
447 | // MARK: - UIView
448 |
449 | /**
450 | Tells the view that its superview changed.
451 | */
452 | open override func didMoveToSuperview() {
453 | render(.normal)
454 | }
455 | }
456 |
457 | extension PermissionButton {
458 | @objc func highlight(_ button: PermissionButton) {
459 | render(.highlighted)
460 | }
461 |
462 | @objc func tapped(_ button: PermissionButton) {
463 | permission.request { [weak self] _ in
464 | self?.render()
465 | }
466 | }
467 | }
468 |
469 | private extension PermissionButton {
470 | func render(_ state: UIControl.State = .normal) {
471 | if let title = titleForStatus(status, andState: state) {
472 | super.setTitle(title, for: state)
473 | }
474 |
475 | if let title = attributedTitleForStatus(status, andState: state) {
476 | super.setAttributedTitle(title, for: state)
477 | }
478 |
479 | if let color = titleColorForStatus(status, andState: state) {
480 | super.setTitleColor(color, for: state)
481 | }
482 |
483 | if let color = titleShadowColorForStatus(status, andState: state) {
484 | super.setTitleShadowColor(color, for: state)
485 | }
486 |
487 | if let image = imageForStatus(status, andState: state) {
488 | super.setImage(image, for: state)
489 | }
490 |
491 | if let image = backgroundImageForStatus(status, andState: state) {
492 | super.setBackgroundImage(image, for: state)
493 | }
494 | }
495 | }
496 |
--------------------------------------------------------------------------------
/Permission.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3D42A7DB1D5F66B300236ABA /* SpeechRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D42A7DA1D5F66B300236ABA /* SpeechRecognizer.swift */; };
11 | 3DC217D31D6EFD4A00600DFE /* MediaLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC217D21D6EFD4A00600DFE /* MediaLibrary.swift */; };
12 | 3F21DC7E1E30E0B900B3EF65 /* Siri.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F21DC7C1E30B6DB00B3EF65 /* Siri.swift */; };
13 | 6D0EBDBD1BFCF8B700C35F8E /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EBDBC1BFCF8B700C35F8E /* Utilities.swift */; };
14 | 6D491E781C9CA90B00611006 /* PermissionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D491E771C9CA90B00611006 /* PermissionStatus.swift */; };
15 | 6D7D08D51C9DFD9D00746121 /* PermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7D08D41C9DFD9D00746121 /* PermissionTests.swift */; };
16 | 6D86A9B61BEBDC7D00E3DD5A /* Permission.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D86A9B51BEBDC7D00E3DD5A /* Permission.h */; settings = {ATTRIBUTES = (Public, ); }; };
17 | 6D86A9BD1BEBDC7D00E3DD5A /* Permission.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D86A9B21BEBDC7D00E3DD5A /* Permission.framework */; };
18 | 6D86A9CD1BEBDC9000E3DD5A /* Permission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D86A9CC1BEBDC9000E3DD5A /* Permission.swift */; };
19 | 6D935F5D1C9A0FEA00BB39E3 /* Bluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D935F5C1C9A0FEA00BB39E3 /* Bluetooth.swift */; };
20 | 6D935F5F1C9A14AB00BB39E3 /* Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D935F5E1C9A14AB00BB39E3 /* Motion.swift */; };
21 | 6DA8B4B01BFB80E9007A94FC /* PermissionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA8B4AF1BFB80E9007A94FC /* PermissionButton.swift */; };
22 | 6DA8B4B21BFB8AD8007A94FC /* PermissionAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA8B4B11BFB8AD8007A94FC /* PermissionAlert.swift */; };
23 | 6DF9C2AF1C8F4F2A000710C1 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2AE1C8F4F2A000710C1 /* Contacts.swift */; };
24 | 6DF9C2B21C8F4F45000710C1 /* LocationAlways.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B11C8F4F45000710C1 /* LocationAlways.swift */; };
25 | 6DF9C2B41C8F4F54000710C1 /* LocationWhenInUse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B31C8F4F54000710C1 /* LocationWhenInUse.swift */; };
26 | 6DF9C2B61C8F4F69000710C1 /* Reminders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B51C8F4F69000710C1 /* Reminders.swift */; };
27 | 6DF9C2B81C8F4F8F000710C1 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B71C8F4F8F000710C1 /* Notifications.swift */; };
28 | 6DF9C2BA1C8F4FAC000710C1 /* Microphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B91C8F4FAC000710C1 /* Microphone.swift */; };
29 | 6DF9C2BC1C8F4FDA000710C1 /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2BB1C8F4FDA000710C1 /* Camera.swift */; };
30 | 6DF9C2BE1C8F4FE5000710C1 /* Photos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2BD1C8F4FE5000710C1 /* Photos.swift */; };
31 | 6DF9C2C01C8F5003000710C1 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2BF1C8F5003000710C1 /* Events.swift */; };
32 | 6DF9C2C61C8F5B4C000710C1 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2C51C8F5B4C000710C1 /* Location.swift */; };
33 | 84742782230896D2007A7922 /* PermissionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84742781230896D2007A7922 /* PermissionSet.swift */; };
34 | 8474278423089751007A7922 /* PermissionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8474278323089751007A7922 /* PermissionType.swift */; };
35 | /* End PBXBuildFile section */
36 |
37 | /* Begin PBXContainerItemProxy section */
38 | 6D86A9BE1BEBDC7D00E3DD5A /* PBXContainerItemProxy */ = {
39 | isa = PBXContainerItemProxy;
40 | containerPortal = 6D86A9A91BEBDC7C00E3DD5A /* Project object */;
41 | proxyType = 1;
42 | remoteGlobalIDString = 6D86A9B11BEBDC7C00E3DD5A;
43 | remoteInfo = Sorry;
44 | };
45 | /* End PBXContainerItemProxy section */
46 |
47 | /* Begin PBXFileReference section */
48 | 3D42A7DA1D5F66B300236ABA /* SpeechRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeechRecognizer.swift; sourceTree = "