├── .gitignore ├── .travis.yml ├── LICENSE ├── Permission.podspec ├── Permission.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Permission.xcscheme │ └── PermissionTests.xcscheme ├── PermissionConfiguration.xcconfig ├── README.md ├── Source ├── Permission.swift ├── PermissionAlert.swift ├── PermissionButton.swift ├── PermissionSet.swift ├── PermissionStatus.swift ├── PermissionType.swift ├── PermissionTypes │ ├── AddressBook.swift │ ├── Bluetooth.swift │ ├── Camera.swift │ ├── Contacts.swift │ ├── Events.swift │ ├── Location.swift │ ├── LocationAlways.swift │ ├── LocationWhenInUse.swift │ ├── MediaLibrary.swift │ ├── Microphone.swift │ ├── Motion.swift │ ├── Notifications.swift │ ├── Photos.swift │ ├── Reminders.swift │ ├── Siri.swift │ └── SpeechRecognizer.swift └── Supporting Files │ ├── Info.plist │ ├── Permission.h │ ├── PermissionFlags.xcconfig │ └── Utilities.swift └── Tests ├── PermissionTests.swift └── Supporting Files └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, 4 | # Objective-C.gitignore & Swift.gitignore 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | # Carthage 32 | Carthage/Build 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | xcode_project: Permission.xcodeproj 8 | xcode_scheme: PermissionTests 9 | osx_image: xcode8 10 | xcode_sdk: iphonesimulator10.0 11 | 12 | script: 13 | - xcodebuild test -project Permission.xcodeproj -scheme PermissionTests -destination "platform=iOS Simulator,name=iPhone 6s" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 Damien (http://delba.io) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Permission.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Permission" 3 | s.version = "2.0.4" 4 | s.license = { :type => "MIT" } 5 | s.homepage = "https://github.com/delba/Permission" 6 | s.author = { "Damien" => "damien@delba.io" } 7 | s.summary = "A unified API to ask for permissions on iOS" 8 | s.source = { :git => "https://github.com/delba/Permission.git", :tag => "v2.0.4" } 9 | 10 | s.weak_framework = 'Speech' 11 | 12 | s.ios.deployment_target = "8.0" 13 | 14 | s.requires_arc = true 15 | 16 | s.default_subspec = 'Core' 17 | 18 | s.subspec 'Core' do |co| 19 | co.source_files = "Source/**/*.{swift, h}" 20 | end 21 | 22 | s.subspec 'AddressBook' do |ab| 23 | ab.dependency 'Permission/Core' 24 | ab.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_ADDRESS_BOOK" } 25 | end 26 | 27 | s.subspec 'Bluetooth' do |bl| 28 | bl.dependency 'Permission/Core' 29 | bl.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_BLUETOOTH" } 30 | end 31 | 32 | s.subspec 'Camera' do |cm| 33 | cm.dependency 'Permission/Core' 34 | cm.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_CAMERA" } 35 | end 36 | 37 | s.subspec 'Contacts' do |cn| 38 | cn.dependency 'Permission/Core' 39 | cn.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_CONTACTS" } 40 | end 41 | 42 | s.subspec 'Events' do |ev| 43 | ev.dependency 'Permission/Core' 44 | ev.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_EVENTS" } 45 | end 46 | 47 | s.subspec 'Location' do |lo| 48 | lo.dependency 'Permission/Core' 49 | lo.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_LOCATION" } 50 | end 51 | 52 | s.subspec 'Microphone' do |mi| 53 | mi.dependency 'Permission/Core' 54 | mi.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_MICROPHONE" } 55 | end 56 | 57 | s.subspec 'Motion' do |mo| 58 | mo.dependency 'Permission/Core' 59 | mo.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_MOTION" } 60 | end 61 | 62 | s.subspec 'Notifications' do |no| 63 | no.dependency 'Permission/Core' 64 | no.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_NOTIFICATIONS" } 65 | end 66 | 67 | s.subspec 'Photos' do |ph| 68 | ph.dependency 'Permission/Core' 69 | ph.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_PHOTOS" } 70 | end 71 | 72 | s.subspec 'Reminders' do |re| 73 | re.dependency 'Permission/Core' 74 | re.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_REMINDERS" } 75 | end 76 | 77 | s.subspec 'SpeechRecognizer' do |rs| 78 | rs.dependency 'Permission/Core' 79 | rs.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_SPEECH_RECOGNIZER" } 80 | end 81 | 82 | s.subspec 'MediaLibrary' do |ml| 83 | ml.dependency 'Permission/Core' 84 | ml.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_MEDIA_LIBRARY" } 85 | end 86 | 87 | s.subspec 'Siri' do |ab| 88 | ab.dependency 'Permission/Core' 89 | ab.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DPERMISSION_SIRI" } 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /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 | 6C04FAF61CCA8F3A00B3F361 /* AddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C04FAF51CCA8F3A00B3F361 /* AddressBook.swift */; }; 14 | 6D0069B41C1868E8002FDB42 /* PermissionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0069B31C1868E8002FDB42 /* PermissionSet.swift */; }; 15 | 6D0EBDBD1BFCF8B700C35F8E /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EBDBC1BFCF8B700C35F8E /* Utilities.swift */; }; 16 | 6D491E761C9CA7DE00611006 /* PermissionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D491E751C9CA7DE00611006 /* PermissionType.swift */; }; 17 | 6D491E781C9CA90B00611006 /* PermissionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D491E771C9CA90B00611006 /* PermissionStatus.swift */; }; 18 | 6D7D08D51C9DFD9D00746121 /* PermissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7D08D41C9DFD9D00746121 /* PermissionTests.swift */; }; 19 | 6D86A9B61BEBDC7D00E3DD5A /* Permission.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D86A9B51BEBDC7D00E3DD5A /* Permission.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | 6D86A9BD1BEBDC7D00E3DD5A /* Permission.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D86A9B21BEBDC7D00E3DD5A /* Permission.framework */; }; 21 | 6D86A9CD1BEBDC9000E3DD5A /* Permission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D86A9CC1BEBDC9000E3DD5A /* Permission.swift */; }; 22 | 6D935F5D1C9A0FEA00BB39E3 /* Bluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D935F5C1C9A0FEA00BB39E3 /* Bluetooth.swift */; }; 23 | 6D935F5F1C9A14AB00BB39E3 /* Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D935F5E1C9A14AB00BB39E3 /* Motion.swift */; }; 24 | 6DA8B4B01BFB80E9007A94FC /* PermissionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA8B4AF1BFB80E9007A94FC /* PermissionButton.swift */; }; 25 | 6DA8B4B21BFB8AD8007A94FC /* PermissionAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA8B4B11BFB8AD8007A94FC /* PermissionAlert.swift */; }; 26 | 6DF9C2AF1C8F4F2A000710C1 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2AE1C8F4F2A000710C1 /* Contacts.swift */; }; 27 | 6DF9C2B21C8F4F45000710C1 /* LocationAlways.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B11C8F4F45000710C1 /* LocationAlways.swift */; }; 28 | 6DF9C2B41C8F4F54000710C1 /* LocationWhenInUse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B31C8F4F54000710C1 /* LocationWhenInUse.swift */; }; 29 | 6DF9C2B61C8F4F69000710C1 /* Reminders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B51C8F4F69000710C1 /* Reminders.swift */; }; 30 | 6DF9C2B81C8F4F8F000710C1 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B71C8F4F8F000710C1 /* Notifications.swift */; }; 31 | 6DF9C2BA1C8F4FAC000710C1 /* Microphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2B91C8F4FAC000710C1 /* Microphone.swift */; }; 32 | 6DF9C2BC1C8F4FDA000710C1 /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2BB1C8F4FDA000710C1 /* Camera.swift */; }; 33 | 6DF9C2BE1C8F4FE5000710C1 /* Photos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2BD1C8F4FE5000710C1 /* Photos.swift */; }; 34 | 6DF9C2C01C8F5003000710C1 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2BF1C8F5003000710C1 /* Events.swift */; }; 35 | 6DF9C2C61C8F5B4C000710C1 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DF9C2C51C8F5B4C000710C1 /* Location.swift */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXContainerItemProxy section */ 39 | 6D86A9BE1BEBDC7D00E3DD5A /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = 6D86A9A91BEBDC7C00E3DD5A /* Project object */; 42 | proxyType = 1; 43 | remoteGlobalIDString = 6D86A9B11BEBDC7C00E3DD5A; 44 | remoteInfo = Sorry; 45 | }; 46 | /* End PBXContainerItemProxy section */ 47 | 48 | /* Begin PBXFileReference section */ 49 | 3D42A7DA1D5F66B300236ABA /* SpeechRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeechRecognizer.swift; sourceTree = ""; }; 50 | 3DC217D21D6EFD4A00600DFE /* MediaLibrary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaLibrary.swift; sourceTree = ""; }; 51 | 3F21DC7C1E30B6DB00B3EF65 /* Siri.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Siri.swift; sourceTree = ""; }; 52 | 6C04FAF51CCA8F3A00B3F361 /* AddressBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBook.swift; sourceTree = ""; }; 53 | 6D0069B31C1868E8002FDB42 /* PermissionSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionSet.swift; sourceTree = ""; }; 54 | 6D0EBDBC1BFCF8B700C35F8E /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; 55 | 6D491E751C9CA7DE00611006 /* PermissionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionType.swift; sourceTree = ""; }; 56 | 6D491E771C9CA90B00611006 /* PermissionStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionStatus.swift; sourceTree = ""; }; 57 | 6D7D08D41C9DFD9D00746121 /* PermissionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionTests.swift; sourceTree = ""; }; 58 | 6D86A9B21BEBDC7D00E3DD5A /* Permission.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Permission.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 6D86A9B51BEBDC7D00E3DD5A /* Permission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Permission.h; sourceTree = ""; }; 60 | 6D86A9B71BEBDC7D00E3DD5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 6D86A9BC1BEBDC7D00E3DD5A /* Permission.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Permission.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 6D86A9C31BEBDC7D00E3DD5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 6D86A9CC1BEBDC9000E3DD5A /* Permission.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Permission.swift; sourceTree = ""; }; 64 | 6D935F5C1C9A0FEA00BB39E3 /* Bluetooth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bluetooth.swift; sourceTree = ""; }; 65 | 6D935F5E1C9A14AB00BB39E3 /* Motion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Motion.swift; sourceTree = ""; }; 66 | 6DA8B4AF1BFB80E9007A94FC /* PermissionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionButton.swift; sourceTree = ""; }; 67 | 6DA8B4B11BFB8AD8007A94FC /* PermissionAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionAlert.swift; sourceTree = ""; }; 68 | 6DF9C2AE1C8F4F2A000710C1 /* Contacts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = ""; }; 69 | 6DF9C2B11C8F4F45000710C1 /* LocationAlways.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationAlways.swift; sourceTree = ""; }; 70 | 6DF9C2B31C8F4F54000710C1 /* LocationWhenInUse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationWhenInUse.swift; sourceTree = ""; }; 71 | 6DF9C2B51C8F4F69000710C1 /* Reminders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reminders.swift; sourceTree = ""; }; 72 | 6DF9C2B71C8F4F8F000710C1 /* Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; 73 | 6DF9C2B91C8F4FAC000710C1 /* Microphone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Microphone.swift; sourceTree = ""; }; 74 | 6DF9C2BB1C8F4FDA000710C1 /* Camera.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Camera.swift; sourceTree = ""; }; 75 | 6DF9C2BD1C8F4FE5000710C1 /* Photos.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Photos.swift; sourceTree = ""; }; 76 | 6DF9C2BF1C8F5003000710C1 /* Events.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; 77 | 6DF9C2C51C8F5B4C000710C1 /* Location.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = ""; }; 78 | D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = PermissionFlags.xcconfig; sourceTree = ""; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | 6D86A9AE1BEBDC7C00E3DD5A /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | 6D86A9B91BEBDC7D00E3DD5A /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | 6D86A9BD1BEBDC7D00E3DD5A /* Permission.framework in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | 6D86A9A81BEBDC7C00E3DD5A = { 101 | isa = PBXGroup; 102 | children = ( 103 | 6D86A9B31BEBDC7D00E3DD5A /* Products */, 104 | 6D86A9B41BEBDC7D00E3DD5A /* Source */, 105 | 6D86A9C01BEBDC7D00E3DD5A /* Tests */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | 6D86A9B31BEBDC7D00E3DD5A /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 6D86A9B21BEBDC7D00E3DD5A /* Permission.framework */, 113 | 6D86A9BC1BEBDC7D00E3DD5A /* Permission.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 6D86A9B41BEBDC7D00E3DD5A /* Source */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 6DF9C2B01C8F4F30000710C1 /* PermissionTypes */, 122 | 6DF9C2C21C8F53C3000710C1 /* Supporting Files */, 123 | 6D86A9CC1BEBDC9000E3DD5A /* Permission.swift */, 124 | 6DA8B4B11BFB8AD8007A94FC /* PermissionAlert.swift */, 125 | 6DA8B4AF1BFB80E9007A94FC /* PermissionButton.swift */, 126 | 6D0069B31C1868E8002FDB42 /* PermissionSet.swift */, 127 | 6D491E771C9CA90B00611006 /* PermissionStatus.swift */, 128 | 6D491E751C9CA7DE00611006 /* PermissionType.swift */, 129 | ); 130 | path = Source; 131 | sourceTree = ""; 132 | }; 133 | 6D86A9C01BEBDC7D00E3DD5A /* Tests */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 6DF9C2C11C8F53B7000710C1 /* Supporting Files */, 137 | 6D7D08D41C9DFD9D00746121 /* PermissionTests.swift */, 138 | ); 139 | path = Tests; 140 | sourceTree = ""; 141 | }; 142 | 6DF9C2B01C8F4F30000710C1 /* PermissionTypes */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 6C04FAF51CCA8F3A00B3F361 /* AddressBook.swift */, 146 | 6D935F5C1C9A0FEA00BB39E3 /* Bluetooth.swift */, 147 | 6DF9C2BB1C8F4FDA000710C1 /* Camera.swift */, 148 | 6DF9C2AE1C8F4F2A000710C1 /* Contacts.swift */, 149 | 6DF9C2BF1C8F5003000710C1 /* Events.swift */, 150 | 6DF9C2C51C8F5B4C000710C1 /* Location.swift */, 151 | 6DF9C2B11C8F4F45000710C1 /* LocationAlways.swift */, 152 | 6DF9C2B31C8F4F54000710C1 /* LocationWhenInUse.swift */, 153 | 3DC217D21D6EFD4A00600DFE /* MediaLibrary.swift */, 154 | 6DF9C2B91C8F4FAC000710C1 /* Microphone.swift */, 155 | 6D935F5E1C9A14AB00BB39E3 /* Motion.swift */, 156 | 6DF9C2B71C8F4F8F000710C1 /* Notifications.swift */, 157 | 6DF9C2BD1C8F4FE5000710C1 /* Photos.swift */, 158 | 6DF9C2B51C8F4F69000710C1 /* Reminders.swift */, 159 | 3F21DC7C1E30B6DB00B3EF65 /* Siri.swift */, 160 | 3D42A7DA1D5F66B300236ABA /* SpeechRecognizer.swift */, 161 | ); 162 | path = PermissionTypes; 163 | sourceTree = ""; 164 | }; 165 | 6DF9C2C11C8F53B7000710C1 /* Supporting Files */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 6D86A9C31BEBDC7D00E3DD5A /* Info.plist */, 169 | ); 170 | path = "Supporting Files"; 171 | sourceTree = ""; 172 | }; 173 | 6DF9C2C21C8F53C3000710C1 /* Supporting Files */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 6D86A9B71BEBDC7D00E3DD5A /* Info.plist */, 177 | 6D86A9B51BEBDC7D00E3DD5A /* Permission.h */, 178 | D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */, 179 | 6D0EBDBC1BFCF8B700C35F8E /* Utilities.swift */, 180 | ); 181 | path = "Supporting Files"; 182 | sourceTree = ""; 183 | }; 184 | /* End PBXGroup section */ 185 | 186 | /* Begin PBXHeadersBuildPhase section */ 187 | 6D86A9AF1BEBDC7C00E3DD5A /* Headers */ = { 188 | isa = PBXHeadersBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 6D86A9B61BEBDC7D00E3DD5A /* Permission.h in Headers */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXHeadersBuildPhase section */ 196 | 197 | /* Begin PBXNativeTarget section */ 198 | 6D86A9B11BEBDC7C00E3DD5A /* Permission */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = 6D86A9C61BEBDC7D00E3DD5A /* Build configuration list for PBXNativeTarget "Permission" */; 201 | buildPhases = ( 202 | 6D86A9AD1BEBDC7C00E3DD5A /* Sources */, 203 | 6D86A9AE1BEBDC7C00E3DD5A /* Frameworks */, 204 | 6D86A9AF1BEBDC7C00E3DD5A /* Headers */, 205 | 6D86A9B01BEBDC7C00E3DD5A /* Resources */, 206 | ); 207 | buildRules = ( 208 | ); 209 | dependencies = ( 210 | ); 211 | name = Permission; 212 | productName = Sorry; 213 | productReference = 6D86A9B21BEBDC7D00E3DD5A /* Permission.framework */; 214 | productType = "com.apple.product-type.framework"; 215 | }; 216 | 6D86A9BB1BEBDC7D00E3DD5A /* PermissionTests */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = 6D86A9C91BEBDC7D00E3DD5A /* Build configuration list for PBXNativeTarget "PermissionTests" */; 219 | buildPhases = ( 220 | 6D86A9B81BEBDC7D00E3DD5A /* Sources */, 221 | 6D86A9B91BEBDC7D00E3DD5A /* Frameworks */, 222 | 6D86A9BA1BEBDC7D00E3DD5A /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | 6D86A9BF1BEBDC7D00E3DD5A /* PBXTargetDependency */, 228 | ); 229 | name = PermissionTests; 230 | productName = SorryTests; 231 | productReference = 6D86A9BC1BEBDC7D00E3DD5A /* Permission.xctest */; 232 | productType = "com.apple.product-type.bundle.unit-test"; 233 | }; 234 | /* End PBXNativeTarget section */ 235 | 236 | /* Begin PBXProject section */ 237 | 6D86A9A91BEBDC7C00E3DD5A /* Project object */ = { 238 | isa = PBXProject; 239 | attributes = { 240 | LastSwiftUpdateCheck = 0710; 241 | LastUpgradeCheck = 0810; 242 | ORGANIZATIONNAME = delba; 243 | TargetAttributes = { 244 | 6D86A9B11BEBDC7C00E3DD5A = { 245 | CreatedOnToolsVersion = 7.1; 246 | LastSwiftMigration = 0800; 247 | }; 248 | 6D86A9BB1BEBDC7D00E3DD5A = { 249 | CreatedOnToolsVersion = 7.1; 250 | LastSwiftMigration = 0800; 251 | }; 252 | }; 253 | }; 254 | buildConfigurationList = 6D86A9AC1BEBDC7C00E3DD5A /* Build configuration list for PBXProject "Permission" */; 255 | compatibilityVersion = "Xcode 3.2"; 256 | developmentRegion = English; 257 | hasScannedForEncodings = 0; 258 | knownRegions = ( 259 | en, 260 | ); 261 | mainGroup = 6D86A9A81BEBDC7C00E3DD5A; 262 | productRefGroup = 6D86A9B31BEBDC7D00E3DD5A /* Products */; 263 | projectDirPath = ""; 264 | projectRoot = ""; 265 | targets = ( 266 | 6D86A9B11BEBDC7C00E3DD5A /* Permission */, 267 | 6D86A9BB1BEBDC7D00E3DD5A /* PermissionTests */, 268 | ); 269 | }; 270 | /* End PBXProject section */ 271 | 272 | /* Begin PBXResourcesBuildPhase section */ 273 | 6D86A9B01BEBDC7C00E3DD5A /* Resources */ = { 274 | isa = PBXResourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | 6D86A9BA1BEBDC7D00E3DD5A /* Resources */ = { 281 | isa = PBXResourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXResourcesBuildPhase section */ 288 | 289 | /* Begin PBXSourcesBuildPhase section */ 290 | 6D86A9AD1BEBDC7C00E3DD5A /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 6D491E781C9CA90B00611006 /* PermissionStatus.swift in Sources */, 295 | 6D935F5F1C9A14AB00BB39E3 /* Motion.swift in Sources */, 296 | 6D935F5D1C9A0FEA00BB39E3 /* Bluetooth.swift in Sources */, 297 | 6DF9C2B81C8F4F8F000710C1 /* Notifications.swift in Sources */, 298 | 6D0069B41C1868E8002FDB42 /* PermissionSet.swift in Sources */, 299 | 6D491E761C9CA7DE00611006 /* PermissionType.swift in Sources */, 300 | 6DF9C2B41C8F4F54000710C1 /* LocationWhenInUse.swift in Sources */, 301 | 6DA8B4B01BFB80E9007A94FC /* PermissionButton.swift in Sources */, 302 | 6DF9C2C01C8F5003000710C1 /* Events.swift in Sources */, 303 | 6DF9C2B61C8F4F69000710C1 /* Reminders.swift in Sources */, 304 | 6DF9C2AF1C8F4F2A000710C1 /* Contacts.swift in Sources */, 305 | 6DF9C2C61C8F5B4C000710C1 /* Location.swift in Sources */, 306 | 3F21DC7E1E30E0B900B3EF65 /* Siri.swift in Sources */, 307 | 6DA8B4B21BFB8AD8007A94FC /* PermissionAlert.swift in Sources */, 308 | 6C04FAF61CCA8F3A00B3F361 /* AddressBook.swift in Sources */, 309 | 3D42A7DB1D5F66B300236ABA /* SpeechRecognizer.swift in Sources */, 310 | 6DF9C2BE1C8F4FE5000710C1 /* Photos.swift in Sources */, 311 | 6DF9C2BA1C8F4FAC000710C1 /* Microphone.swift in Sources */, 312 | 6DF9C2BC1C8F4FDA000710C1 /* Camera.swift in Sources */, 313 | 6DF9C2B21C8F4F45000710C1 /* LocationAlways.swift in Sources */, 314 | 3DC217D31D6EFD4A00600DFE /* MediaLibrary.swift in Sources */, 315 | 6D86A9CD1BEBDC9000E3DD5A /* Permission.swift in Sources */, 316 | 6D0EBDBD1BFCF8B700C35F8E /* Utilities.swift in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | 6D86A9B81BEBDC7D00E3DD5A /* Sources */ = { 321 | isa = PBXSourcesBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | 6D7D08D51C9DFD9D00746121 /* PermissionTests.swift in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | /* End PBXSourcesBuildPhase section */ 329 | 330 | /* Begin PBXTargetDependency section */ 331 | 6D86A9BF1BEBDC7D00E3DD5A /* PBXTargetDependency */ = { 332 | isa = PBXTargetDependency; 333 | target = 6D86A9B11BEBDC7C00E3DD5A /* Permission */; 334 | targetProxy = 6D86A9BE1BEBDC7D00E3DD5A /* PBXContainerItemProxy */; 335 | }; 336 | /* End PBXTargetDependency section */ 337 | 338 | /* Begin XCBuildConfiguration section */ 339 | 6D86A9C41BEBDC7D00E3DD5A /* Debug */ = { 340 | isa = XCBuildConfiguration; 341 | baseConfigurationReference = D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */; 342 | buildSettings = { 343 | ALWAYS_SEARCH_USER_PATHS = NO; 344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 345 | CLANG_CXX_LIBRARY = "libc++"; 346 | CLANG_ENABLE_MODULES = YES; 347 | CLANG_ENABLE_OBJC_ARC = YES; 348 | CLANG_WARN_BOOL_CONVERSION = YES; 349 | CLANG_WARN_CONSTANT_CONVERSION = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INFINITE_RECURSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNREACHABLE_CODE = YES; 358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 359 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 360 | COPY_PHASE_STRIP = NO; 361 | CURRENT_PROJECT_VERSION = 1; 362 | DEBUG_INFORMATION_FORMAT = dwarf; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | ENABLE_TESTABILITY = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_DYNAMIC_NO_PIC = NO; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_OPTIMIZATION_LEVEL = 0; 369 | GCC_PREPROCESSOR_DEFINITIONS = ( 370 | "DEBUG=1", 371 | "$(inherited)", 372 | ); 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 380 | MTL_ENABLE_DEBUG_INFO = YES; 381 | ONLY_ACTIVE_ARCH = YES; 382 | SDKROOT = iphoneos; 383 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(INHERITED)"; 384 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 385 | TARGETED_DEVICE_FAMILY = "1,2"; 386 | VERSIONING_SYSTEM = "apple-generic"; 387 | VERSION_INFO_PREFIX = ""; 388 | }; 389 | name = Debug; 390 | }; 391 | 6D86A9C51BEBDC7D00E3DD5A /* Release */ = { 392 | isa = XCBuildConfiguration; 393 | baseConfigurationReference = D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */; 394 | buildSettings = { 395 | ALWAYS_SEARCH_USER_PATHS = NO; 396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 397 | CLANG_CXX_LIBRARY = "libc++"; 398 | CLANG_ENABLE_MODULES = YES; 399 | CLANG_ENABLE_OBJC_ARC = YES; 400 | CLANG_WARN_BOOL_CONVERSION = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INFINITE_RECURSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 408 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 409 | CLANG_WARN_UNREACHABLE_CODE = YES; 410 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 411 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 412 | COPY_PHASE_STRIP = NO; 413 | CURRENT_PROJECT_VERSION = 1; 414 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 415 | ENABLE_NS_ASSERTIONS = NO; 416 | ENABLE_STRICT_OBJC_MSGSEND = YES; 417 | GCC_C_LANGUAGE_STANDARD = gnu99; 418 | GCC_NO_COMMON_BLOCKS = YES; 419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 421 | GCC_WARN_UNDECLARED_SELECTOR = YES; 422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 423 | GCC_WARN_UNUSED_FUNCTION = YES; 424 | GCC_WARN_UNUSED_VARIABLE = YES; 425 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 426 | MTL_ENABLE_DEBUG_INFO = NO; 427 | SDKROOT = iphoneos; 428 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(INHERITED)"; 429 | TARGETED_DEVICE_FAMILY = "1,2"; 430 | VALIDATE_PRODUCT = YES; 431 | VERSIONING_SYSTEM = "apple-generic"; 432 | VERSION_INFO_PREFIX = ""; 433 | }; 434 | name = Release; 435 | }; 436 | 6D86A9C71BEBDC7D00E3DD5A /* Debug */ = { 437 | isa = XCBuildConfiguration; 438 | baseConfigurationReference = D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */; 439 | buildSettings = { 440 | CLANG_ENABLE_MODULES = YES; 441 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 442 | DEFINES_MODULE = YES; 443 | DYLIB_COMPATIBILITY_VERSION = 1; 444 | DYLIB_CURRENT_VERSION = 1; 445 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 446 | GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; 447 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 448 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 449 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 451 | PRODUCT_BUNDLE_IDENTIFIER = io.delba.Permission; 452 | PRODUCT_NAME = Permission; 453 | SKIP_INSTALL = YES; 454 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 455 | SWIFT_VERSION = 3.0; 456 | }; 457 | name = Debug; 458 | }; 459 | 6D86A9C81BEBDC7D00E3DD5A /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | baseConfigurationReference = D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */; 462 | buildSettings = { 463 | CLANG_ENABLE_MODULES = YES; 464 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 465 | DEFINES_MODULE = YES; 466 | DYLIB_COMPATIBILITY_VERSION = 1; 467 | DYLIB_CURRENT_VERSION = 1; 468 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 469 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 470 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 471 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 472 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 473 | PRODUCT_BUNDLE_IDENTIFIER = io.delba.Permission; 474 | PRODUCT_NAME = Permission; 475 | SKIP_INSTALL = YES; 476 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 477 | SWIFT_VERSION = 3.0; 478 | }; 479 | name = Release; 480 | }; 481 | 6D86A9CA1BEBDC7D00E3DD5A /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | baseConfigurationReference = D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */; 484 | buildSettings = { 485 | INFOPLIST_FILE = "Tests/Supporting Files/Info.plist"; 486 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 487 | PRODUCT_BUNDLE_IDENTIFIER = io.delba.PermissionTests; 488 | PRODUCT_NAME = Permission; 489 | SWIFT_VERSION = 3.0; 490 | }; 491 | name = Debug; 492 | }; 493 | 6D86A9CB1BEBDC7D00E3DD5A /* Release */ = { 494 | isa = XCBuildConfiguration; 495 | baseConfigurationReference = D08FF2891DC3AD2900F28088 /* PermissionFlags.xcconfig */; 496 | buildSettings = { 497 | INFOPLIST_FILE = "Tests/Supporting Files/Info.plist"; 498 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 499 | PRODUCT_BUNDLE_IDENTIFIER = io.delba.PermissionTests; 500 | PRODUCT_NAME = Permission; 501 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 502 | SWIFT_VERSION = 3.0; 503 | }; 504 | name = Release; 505 | }; 506 | /* End XCBuildConfiguration section */ 507 | 508 | /* Begin XCConfigurationList section */ 509 | 6D86A9AC1BEBDC7C00E3DD5A /* Build configuration list for PBXProject "Permission" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | 6D86A9C41BEBDC7D00E3DD5A /* Debug */, 513 | 6D86A9C51BEBDC7D00E3DD5A /* Release */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | 6D86A9C61BEBDC7D00E3DD5A /* Build configuration list for PBXNativeTarget "Permission" */ = { 519 | isa = XCConfigurationList; 520 | buildConfigurations = ( 521 | 6D86A9C71BEBDC7D00E3DD5A /* Debug */, 522 | 6D86A9C81BEBDC7D00E3DD5A /* Release */, 523 | ); 524 | defaultConfigurationIsVisible = 0; 525 | defaultConfigurationName = Release; 526 | }; 527 | 6D86A9C91BEBDC7D00E3DD5A /* Build configuration list for PBXNativeTarget "PermissionTests" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | 6D86A9CA1BEBDC7D00E3DD5A /* Debug */, 531 | 6D86A9CB1BEBDC7D00E3DD5A /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | /* End XCConfigurationList section */ 537 | }; 538 | rootObject = 6D86A9A91BEBDC7C00E3DD5A /* Project object */; 539 | } 540 | -------------------------------------------------------------------------------- /Permission.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Permission.xcodeproj/xcshareddata/xcschemes/Permission.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Permission.xcodeproj/xcshareddata/xcschemes/PermissionTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /PermissionConfiguration.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionConfiguration.swift 3 | // 4 | // Copyright (c) 2015-2016 Justin Makaila (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 | PERMISSION_ADDRESS_BOOK = PERMISSION_ADDRESS_BOOK 26 | PERMISSION_BLUETOOTH = PERMISSION_BLUETOOTH 27 | PERMISSION_CAMERA = PERMISSION_CAMERA 28 | PERMISSION_CONTACTS = PERMISSION_CONTACTS 29 | PERMISSION_EVENTS = PERMISSION_EVENTS 30 | PERMISSION_LOCATION = PERMISSION_LOCATION 31 | PERMISSION_MICROPHONE = PERMISSION_MICROPHONE 32 | PERMISSION_MOTION = PERMISSION_MOTION 33 | PERMISSION_NOTIFICATIONS = PERMISSION_NOTIFICATIONS 34 | PERMISSION_PHOTOS = PERMISSION_PHOTOS 35 | PERMISSION_REMINDERS = PERMISSION_REMINDERS 36 | PERMISSION_SPEECH_RECOGNIZER = PERMISSION_SPEECH_RECOGNIZER 37 | PERMISSION_MEDIA_LIBRARY = PERMISSION_MEDIA_LIBRARY 38 | PERMISSION_SIRI = PERMISSION_SIRI 39 | 40 | // Do not modify this line. Instead, remove comments above as needed to enable the categories your app uses. 41 | PERMISSION_FLAGS = $(PERMISSION_ADDRESS_BOOK) $(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) $(PERMISSION_SIRI) 42 | 43 | SWIFT_ACTIVE_COMPILATION_CONDITIONS= $(inherited) $(PERMISSION_FLAGS) 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Travis Status 7 | CocoaPods compatible 8 | Carthage compatible 9 |

10 | 11 | **Permission** exposes a unified API to request permissions on iOS. 12 | 13 |

14 | UsageExampleInstallationLicense 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) // PermissionStatus.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 | > [`PermissionTypes/`](https://github.com/delba/Permission/tree/master/Source/PermissionTypes) 43 | 44 | - [`AddressBook`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/AddressBook.swift) (Deprecated in iOS 9.0) 45 | - [`Bluetooth`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Bluetooth.swift) 46 | - [`Camera`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Camera.swift) 47 | - [`Contacts`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Contacts.swift) 48 | - [`Events`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Events.swift) 49 | - [`Motion`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Motion.swift) 50 | - [`Microphone`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Microphone.swift) 51 | - [`Notifications`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Notifications.swift) 52 | - [`Photos`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Photos.swift) 53 | - [`Reminders`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Reminders.swift) 54 | - [`LocationAlways`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/LocationAlways.swift) 55 | - [`LocationWhenInUse`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/LocationWhenInUse.swift) 56 | - [`MediaLibrary`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/MediaLibrary.swift) 57 | - [`SpeechRecognizer`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/SpeechRecognizer.swift) 58 | - [`Siri`](https://github.com/delba/Permission/blob/master/Source/PermissionTypes/Siri.swift) 59 | 60 | #### PermissionAlert 61 | 62 | > [`PermissionAlert.swift`](https://github.com/delba/Permission/blob/master/Source/PermissionAlert.swift) 63 | 64 | ##### Denied and disabled alerts 65 | 66 | When you first request a permission, a system alert is presented to the user. 67 | If you request a permission that was denied/disabled, a `PermissionAlert` will be presented. 68 | You might want to change the default `title`, `message`, `cancel` and `settings` text: 69 | 70 | ```swift 71 | let alert = permission.deniedAlert // or permission.disabledAlert 72 | 73 | alert.title = "Please allow access to your contacts" 74 | alert.message = nil 75 | alert.cancel = "Cancel" 76 | alert.settings = "Settings" 77 | ``` 78 | 79 | Set `permission.presentDeniedAlert = false` or `permission.presentDisabledAlert = false` if you don't want to present these alerts. 80 | 81 | ##### Pre-permission alerts 82 | 83 | 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. 84 | 85 | ```swift 86 | permission.presentPrePermissionAlert = true 87 | 88 | let alert = permission.prePermissionAlert 89 | 90 | alert.title = "Let Foo Access Photos?" 91 | alert.message = "This lets you choose which photos you want to add to your Foo profile" 92 | alert.cancel = "Not now" 93 | alert.confirm = "Give Access" 94 | ``` 95 | 96 | The system alert will only be presented if the user taps "Give Access". 97 | 98 | #### PermissionSet 99 | 100 | > [`PermissionSet.swift`](https://github.com/delba/Permission/blob/master/Source/PermissionSet.swift) 101 | 102 | Use a `PermissionSet` to check the status of a group of `Permission` and to react when a permission is requested. 103 | 104 | ```swift 105 | let permissionSet = PermissionSet(.contacts, .camera, .microphone, .photos) 106 | permissionSet.delegate = self 107 | 108 | print(permissionSet.status) // PermissionStatus.NotDetermined 109 | 110 | // ... 111 | 112 | func permissionSet(permissionSet: PermissionSet, willRequestPermission permission: Permission) { 113 | print("Will request \(permission)") 114 | } 115 | 116 | func permissionSet(permissionSet: PermissionSet, didRequestPermission permission: Permission) { 117 | switch permissionSet.status { 118 | case .authorized: print("all the permissions are granted") 119 | case .denied: print("at least one permission is denied") 120 | case .disabled: print("at least one permission is disabled") 121 | case .notDetermined: print("at least one permission is not determined") 122 | } 123 | } 124 | ``` 125 | 126 | #### PermissionButton 127 | 128 | > [`PermissionButton`](https://github.com/delba/Permission/blob/master/Source/PermissionButton.swift) 129 | 130 | A `PermissionButton` requests the permission when tapped and updates itself when its underlying permission status changes. 131 | 132 | ```swift 133 | let button = PermissionButton(.photos) 134 | ``` 135 | 136 | `PermissionButton` is a subclass of `UIButton`. All the getters and setters of `UIButton` have their equivalent in `PermissionButton`. 137 | 138 | ```swift 139 | button.setTitles([ 140 | .authorized: "Authorized", 141 | .denied: "Denied", 142 | .disabled: "Disabled", 143 | .notDetermined: "Not determined" 144 | ]) 145 | 146 | // button.setAttributedTitles 147 | // button.setTitleColors 148 | // button.setTitleShadowColors 149 | // button.setImages 150 | // button.setBackgroundImages 151 | // etc. 152 | ``` 153 | 154 | #### Third-party libraries: 155 | 156 | - [sunshinejr/**RxPermission**](https://github.com/sunshinejr/RxPermission) RxSwift bindings for Permissions API in iOS. 157 | 158 | ## Example 159 | 160 | ```swift 161 | class PermissionsViewController: UIViewController, PermissionSetDelegate { 162 | 163 | override func viewDidLoad() { 164 | super.viewDidLoad() 165 | 166 | let label = UILabel() 167 | 168 | let contacts = PermissionButton(.contacts) 169 | let camera = PermissionButton(.camera) 170 | let microphone = PermissionButton(.microphone) 171 | let photos = PermissionButton(.photos) 172 | 173 | contacts.setTitles([ 174 | .notDetermined: "Contacts - NotDetermined" 175 | .authorized: "Contacts - Authorized", 176 | .denied: "Contacts - Denied" 177 | ]) 178 | 179 | contacts.setTitleColors([ 180 | .notDetermined: .black, 181 | .authorized: .green, 182 | .denied: .red 183 | ]) 184 | 185 | // ... 186 | 187 | let permissionSet = PermissionSet(contacts, camera, microphone, photos) 188 | 189 | permissionSet.delegate = self 190 | 191 | label.text = String(permissionSet.status) 192 | 193 | for subview in [label, contacts, camera, microphone, photos] { 194 | view.addSubview(subview) 195 | } 196 | } 197 | 198 | func permissionSet(permissionSet: PermissionSet, didRequestPermission permission: Permission) { 199 | label.text = String(permissionSet.status) 200 | } 201 | } 202 | ``` 203 | 204 | 205 | 206 | ## Installation 207 | 208 | ### Carthage 209 | 210 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. 211 | 212 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 213 | 214 | ```bash 215 | $ brew update 216 | $ brew install carthage 217 | ``` 218 | 219 | To integrate Permission into your Xcode project using Carthage, specify it in your `Cartfile`: 220 | 221 | ```ogdl 222 | github "delba/Permission" 223 | ``` 224 | 225 | ##### Configuration 226 | 227 | Due to Apple's new policy regarding permission access, binaries may be rejected due to a perceived attempt 228 | to access privacy-sensitive data without a usage key, and then further rejected for not actually requesting 229 | permissions. 230 | 231 | As a workaround, you can provide custom build flags _before_ building the dynamic framework to only compile 232 | with permissions you request. This is done by adding a configuration file named `PermissionConfiguration.xcconfig` 233 | to the root of your project. For convenience, you can use 234 | `PermissionConfiguration.xcconfig` in the `Permission/` repo directory. Just comment out the permissions 235 | you want to use, and compile the framework. 236 | 237 | To compile with only notifications and photos permissions: 238 | ``` 239 | PERMISSION_ADDRESS_BOOK = // PERMISSION_ADDRESS_BOOK 240 | PERMISSION_BLUETOOTH = // PERMISSION_BLUETOOTH 241 | PERMISSION_CAMERA = PERMISSION_CAMERA 242 | PERMISSION_CONTACTS = // PERMISSION_CONTACTS 243 | PERMISSION_EVENTS = // PERMISSION_EVENTS 244 | PERMISSION_LOCATION = // PERMISSION_LOCATION 245 | PERMISSION_MICROPHONE = // PERMISSION_MICROPHONE 246 | PERMISSION_MOTION = // PERMISSION_MOTION 247 | PERMISSION_NOTIFICATIONS = PERMISSION_NOTIFICATIONS 248 | PERMISSION_PHOTOS = // PERMISSION_PHOTOS 249 | PERMISSION_REMINDERS = // PERMISSION_REMINDERS 250 | PERMISSION_SPEECH_RECOGNIZER = // PERMISSION_SPEECH_RECOGNIZER 251 | PERMISSION_MEDIA_LIBRARY = // PERMISSION_MEDIA_LIBRARY 252 | 253 | // Do not modify this line. Instead, remove comments above as needed to enable the categories your app uses. 254 | PERMISSION_FLAGS= $(PERMISSION_ADDRESS_BOOK) $(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) 255 | 256 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) $(PERMISSION_FLAGS) 257 | ``` 258 | 259 | ### Cocoapods 260 | 261 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. 262 | 263 | You can install it with the following command: 264 | 265 | ```bash 266 | $ gem install cocoapods 267 | ``` 268 | 269 | 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: 270 | 271 | ```ruby 272 | use_frameworks! 273 | 274 | pod 'Permission/Camera' 275 | pod 'Permission/Notifications' 276 | ``` 277 | 278 | Please see `Permission.podspec` for more information about which subspecs are available. 279 | 280 | ## License 281 | 282 | Copyright (c) 2015-2016 Damien (http://delba.io) 283 | 284 | Permission is hereby granted, free of charge, to any person obtaining a copy 285 | of this software and associated documentation files (the "Software"), to deal 286 | in the Software without restriction, including without limitation the rights 287 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 288 | copies of the Software, and to permit persons to whom the Software is 289 | furnished to do so, subject to the following conditions: 290 | 291 | The above copyright notice and this permission notice shall be included in all 292 | copies or substantial portions of the Software. 293 | 294 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 295 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 296 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 297 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 298 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 299 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 300 | SOFTWARE. 301 | -------------------------------------------------------------------------------- /Source/Permission.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.swift 3 | // 4 | // Copyright (c) 2015-2016 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 Permission: NSObject { 26 | public typealias Callback = (PermissionStatus) -> Void 27 | 28 | #if PERMISSION_CONTACTS 29 | /// The permission to access the user's contacts. 30 | @available(iOS 9.0, *) 31 | open static let contacts = Permission(type: .contacts) 32 | #endif 33 | 34 | #if PERMISSION_ADDRESS_BOOK 35 | /// The permission to access the user's address book. (Deprecated in iOS 9.0) 36 | open static let addressBook = Permission(type: .addressBook) 37 | #endif 38 | 39 | #if PERMISSION_LOCATION 40 | /// The permission to access the user's location when the app is in background. 41 | open static let locationAlways = Permission(type: .locationAlways) 42 | 43 | /// The permission to access the user's location when the app is in use. 44 | open static let locationWhenInUse = Permission(type: .locationWhenInUse) 45 | #endif 46 | 47 | #if PERMISSION_MICROPHONE 48 | /// The permission to access the microphone. 49 | open static let microphone = Permission(type: .microphone) 50 | #endif 51 | 52 | #if PERMISSION_CAMERA 53 | /// The permission to access the camera. 54 | open static let camera = Permission(type: .camera) 55 | #endif 56 | 57 | #if PERMISSION_PHOTOS 58 | /// The permission to access the user's photos. 59 | open static let photos = Permission(type: .photos) 60 | #endif 61 | 62 | #if PERMISSION_REMINDERS 63 | /// The permission to access the user's reminders. 64 | open static let reminders = Permission(type: .reminders) 65 | #endif 66 | 67 | #if PERMISSION_EVENTS 68 | /// The permission to access the user's events. 69 | open static let events = Permission(type: .events) 70 | #endif 71 | 72 | #if PERMISSION_BLUETOOTH 73 | /// The permission to access the user's bluetooth. 74 | open static let bluetooth = Permission(type: .bluetooth) 75 | #endif 76 | 77 | #if PERMISSION_MOTION 78 | /// The permission to access the user's motion. 79 | open static let motion = Permission(type: .motion) 80 | #endif 81 | 82 | #if PERMISSION_SPEECH_RECOGNIZER 83 | /// The permission to access the user's SpeechRecognizer. 84 | @available(iOS 10.0, *) 85 | open static let speechRecognizer = Permission(type: .speechRecognizer) 86 | #endif 87 | 88 | #if PERMISSION_MEDIA_LIBRARY 89 | /// The permission to access the user's MediaLibrary. 90 | @available(iOS 9.3, *) 91 | open static let mediaLibrary = Permission(type: .mediaLibrary) 92 | #endif 93 | 94 | #if PERMISSION_SIRI 95 | /// The permission to access the user's Siri. 96 | @available(iOS 10.0, *) 97 | open static let siri = Permission(type: .siri) 98 | #endif 99 | 100 | #if PERMISSION_NOTIFICATIONS 101 | /// The permission to send notifications. 102 | open static let notifications: Permission = { 103 | let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil) 104 | return Permission(type: .notifications(settings)) 105 | }() 106 | 107 | /// Variable used to retain the notifications permission. 108 | fileprivate static var _notifications: Permission? 109 | 110 | /// The permission to send notifications. 111 | open static func notifications(types: UIUserNotificationType, categories: Set?) -> Permission { 112 | let settings = UIUserNotificationSettings(types: types, categories: categories) 113 | let permission = Permission(type: .notifications(settings)) 114 | _notifications = permission 115 | return permission 116 | } 117 | 118 | /// The permission to send notifications. 119 | open static func notifications(types: UIUserNotificationType) -> Permission { 120 | let settings = UIUserNotificationSettings(types: types, categories: nil) 121 | let permission = Permission(type: .notifications(settings)) 122 | _notifications = permission 123 | return permission 124 | } 125 | 126 | /// The permission to send notifications. 127 | open static func notifications(categories: Set?) -> Permission { 128 | let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: categories) 129 | let permission = Permission(type: .notifications(settings)) 130 | _notifications = permission 131 | return permission 132 | } 133 | #endif 134 | 135 | /// The permission domain. 136 | open let type: PermissionType 137 | 138 | /// The permission status. 139 | open var status: PermissionStatus { 140 | #if PERMISSION_CONTACTS 141 | if case .contacts = type { return statusContacts } 142 | #endif 143 | 144 | #if PERMISSION_ADDRESS_BOOK 145 | if case .addressBook = type { return statusAddressBook } 146 | #endif 147 | 148 | #if PERMISSION_LOCATION 149 | if case .locationAlways = type { return statusLocationAlways } 150 | if case .locationWhenInUse = type { return statusLocationWhenInUse } 151 | #endif 152 | 153 | #if PERMISSION_NOTIFICATIONS 154 | if case .notifications = type { return statusNotifications } 155 | #endif 156 | 157 | #if PERMISSION_MICROPHONE 158 | if case .microphone = type { return statusMicrophone } 159 | #endif 160 | 161 | #if PERMISSION_CAMERA 162 | if case .camera = type { return statusCamera } 163 | #endif 164 | 165 | #if PERMISSION_PHOTOS 166 | if case .photos = type { return statusPhotos } 167 | #endif 168 | 169 | #if PERMISSION_REMINDERS 170 | if case .reminders = type { return statusReminders } 171 | #endif 172 | 173 | #if PERMISSION_EVENTS 174 | if case .events = type { return statusEvents } 175 | #endif 176 | 177 | #if PERMISSION_BLUETOOTH 178 | if case .bluetooth = type { return statusBluetooth } 179 | #endif 180 | 181 | #if PERMISSION_MOTION 182 | if case .motion = type { return statusMotion } 183 | #endif 184 | 185 | #if PERMISSION_SPEECH_RECOGNIZER 186 | if case .speechRecognizer = type { return statusSpeechRecognizer } 187 | #endif 188 | 189 | #if PERMISSION_MEDIA_LIBRARY 190 | if case .mediaLibrary = type { return statusMediaLibrary } 191 | #endif 192 | 193 | #if PERMISSION_SIRI 194 | if case .siri = type { return statusSiri } 195 | #endif 196 | 197 | fatalError() 198 | } 199 | 200 | /// Determines whether to present the pre-permission alert. 201 | open var presentPrePermissionAlert = false 202 | 203 | /// The pre-permission alert. 204 | open lazy var prePermissionAlert: PermissionAlert = { 205 | return PrePermissionAlert(permission: self) 206 | }() 207 | 208 | /// Determines whether to present the denied alert. 209 | open var presentDeniedAlert = true 210 | 211 | /// The alert when the permission was denied. 212 | open lazy var deniedAlert: PermissionAlert = { 213 | return DeniedAlert(permission: self) 214 | }() 215 | 216 | /// Determines whether to present the disabled alert. 217 | open var presentDisabledAlert = true 218 | 219 | /// The alert when the permission is disabled. 220 | open lazy var disabledAlert: PermissionAlert = { 221 | return DisabledAlert(permission: self) 222 | }() 223 | 224 | internal var callback: Callback? 225 | 226 | internal var permissionSets: [PermissionSet] = [] 227 | 228 | /** 229 | Creates and return a new permission for the specified domain. 230 | 231 | - parameter domain: The permission domain. 232 | 233 | - returns: A newly created permission. 234 | */ 235 | fileprivate init(type: PermissionType) { 236 | self.type = type 237 | } 238 | 239 | /** 240 | Requests the permission. 241 | 242 | - parameter callback: The function to be triggered after the user responded to the request. 243 | */ 244 | open func request(_ callback: @escaping Callback) { 245 | self.callback = callback 246 | 247 | DispatchQueue.main.async { 248 | self.permissionSets.forEach { $0.willRequestPermission(self) } 249 | } 250 | 251 | let status = self.status 252 | 253 | switch status { 254 | case .authorized: callbacks(status) 255 | case .notDetermined: presentPrePermissionAlert ? prePermissionAlert.present() : requestAuthorization(callbacks) 256 | case .denied: presentDeniedAlert ? deniedAlert.present() : callbacks(status) 257 | case .disabled: presentDisabledAlert ? disabledAlert.present() : callbacks(status) 258 | } 259 | } 260 | 261 | internal func requestAuthorization(_ callback: @escaping Callback) { 262 | #if PERMISSION_CONTACTS 263 | if case .contacts = type { 264 | requestContacts(callback) 265 | return 266 | } 267 | #endif 268 | 269 | #if PERMISSION_ADDRESS_BOOK 270 | if case .addressBook = type { 271 | requestAddressBook(callback) 272 | return 273 | } 274 | #endif 275 | 276 | #if PERMISSION_LOCATION 277 | if case .locationAlways = type { 278 | requestLocationAlways(callback) 279 | return 280 | } 281 | 282 | if case .locationWhenInUse = type { 283 | requestLocationWhenInUse(callback) 284 | return 285 | } 286 | #endif 287 | 288 | #if PERMISSION_NOTIFICATIONS 289 | if case .notifications = type { 290 | requestNotifications(callback) 291 | return 292 | } 293 | #endif 294 | 295 | #if PERMISSION_MICROPHONE 296 | if case .microphone = type { 297 | requestMicrophone(callback) 298 | return 299 | } 300 | #endif 301 | 302 | #if PERMISSION_CAMERA 303 | if case .camera = type { 304 | requestCamera(callback) 305 | return 306 | } 307 | #endif 308 | 309 | #if PERMISSION_PHOTOS 310 | if case .photos = type { 311 | requestPhotos(callback) 312 | return 313 | } 314 | #endif 315 | 316 | #if PERMISSION_REMINDERS 317 | if case .reminders = type { 318 | requestReminders(callback) 319 | return 320 | } 321 | #endif 322 | 323 | #if PERMISSION_EVENTS 324 | if case .events = type { 325 | requestEvents(callback) 326 | return 327 | } 328 | #endif 329 | 330 | #if PERMISSION_BLUETOOTH 331 | if case .bluetooth = type { 332 | requestBluetooth(self.callback) 333 | return 334 | } 335 | #endif 336 | 337 | #if PERMISSION_MOTION 338 | if case .motion = type { 339 | requestMotion(self.callback) 340 | return 341 | } 342 | #endif 343 | 344 | #if PERMISSION_SPEECH_RECOGNIZER 345 | if case .speechRecognizer = type { 346 | requestSpeechRecognizer(callback) 347 | return 348 | } 349 | #endif 350 | 351 | #if PERMISSION_MEDIA_LIBRARY 352 | if case .mediaLibrary = type { 353 | requestMediaLibrary(callback) 354 | return 355 | } 356 | #endif 357 | 358 | #if PERMISSION_SIRI 359 | if case .siri = type { 360 | requestSiri(callback) 361 | return 362 | } 363 | #endif 364 | 365 | fatalError() 366 | } 367 | 368 | internal func callbacks(_ with: PermissionStatus) { 369 | DispatchQueue.main.async { 370 | self.callback?(self.status) 371 | self.permissionSets.forEach { $0.didRequestPermission(self) } 372 | } 373 | } 374 | } 375 | 376 | extension Permission { 377 | /// The textual representation of self. 378 | override open var description: String { 379 | return type.description 380 | } 381 | 382 | /// A textual representation of this instance, suitable for debugging. 383 | override open var debugDescription: String { 384 | return "\(type): \(status)" 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /Source/PermissionAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionAlert.swift 3 | // 4 | // Copyright (c) 2015-2016 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 PermissionAlert { 26 | /// The permission. 27 | fileprivate let permission: Permission 28 | 29 | /// The status of the permission. 30 | fileprivate var status: PermissionStatus { return permission.status } 31 | 32 | /// The domain of the permission. 33 | fileprivate var type: PermissionType { return permission.type } 34 | 35 | fileprivate var callbacks: Permission.Callback { return permission.callbacks } 36 | 37 | /// The title of the alert. 38 | open var title: String? 39 | 40 | /// Descriptive text that provides more details about the reason for the alert. 41 | open var message: String? 42 | 43 | /// The title of the cancel action. 44 | open var cancel: String? { 45 | get { return cancelActionTitle } 46 | set { cancelActionTitle = newValue } 47 | } 48 | 49 | /// The title of the settings action. 50 | open var settings: String? { 51 | get { return defaultActionTitle } 52 | set { defaultActionTitle = newValue } 53 | } 54 | 55 | /// The title of the confirm action. 56 | open var confirm: String? { 57 | get { return defaultActionTitle } 58 | set { defaultActionTitle = newValue } 59 | } 60 | 61 | fileprivate var cancelActionTitle: String? 62 | fileprivate var defaultActionTitle: String? 63 | 64 | var controller: UIAlertController { 65 | let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) 66 | 67 | let action = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: cancelHandler) 68 | controller.addAction(action) 69 | 70 | return controller 71 | } 72 | 73 | internal init(permission: Permission) { 74 | self.permission = permission 75 | } 76 | 77 | internal func present() { 78 | DispatchQueue.main.async { 79 | UIApplication.shared.presentViewController(self.controller) 80 | } 81 | } 82 | 83 | fileprivate func cancelHandler(_ action: UIAlertAction) { 84 | callbacks(status) 85 | } 86 | } 87 | 88 | internal class DisabledAlert: PermissionAlert { 89 | override init(permission: Permission) { 90 | super.init(permission: permission) 91 | 92 | title = "\(permission) is currently disabled" 93 | message = "Please enable access to \(permission) in the Settings app." 94 | cancel = "OK" 95 | } 96 | } 97 | 98 | internal class DeniedAlert: PermissionAlert { 99 | override var controller: UIAlertController { 100 | let controller = super.controller 101 | 102 | let action = UIAlertAction(title: defaultActionTitle, style: .default, handler: settingsHandler) 103 | controller.addAction(action) 104 | 105 | if #available(iOS 9.0, *) { 106 | controller.preferredAction = action 107 | } 108 | 109 | return controller 110 | } 111 | 112 | override init(permission: Permission) { 113 | super.init(permission: permission) 114 | 115 | title = "Permission for \(permission) was denied" 116 | message = "Please enable access to \(permission) in the Settings app." 117 | cancel = "Cancel" 118 | settings = "Settings" 119 | } 120 | 121 | @objc func settingsHandler() { 122 | NotificationCenter.default.removeObserver(self, name: .UIApplicationDidBecomeActive) 123 | callbacks(status) 124 | } 125 | 126 | private func settingsHandler(_ action: UIAlertAction) { 127 | NotificationCenter.default.addObserver(self, selector: .settingsHandler, name: .UIApplicationDidBecomeActive) 128 | 129 | if let URL = URL(string: UIApplicationOpenSettingsURLString) { 130 | UIApplication.shared.openURL(URL) 131 | } 132 | } 133 | } 134 | 135 | internal class PrePermissionAlert: PermissionAlert { 136 | override var controller: UIAlertController { 137 | let controller = super.controller 138 | 139 | let action = UIAlertAction(title: defaultActionTitle, style: .default, handler: confirmHandler) 140 | controller.addAction(action) 141 | 142 | if #available(iOS 9.0, *) { 143 | controller.preferredAction = action 144 | } 145 | 146 | return controller 147 | } 148 | 149 | override init(permission: Permission) { 150 | super.init(permission: permission) 151 | 152 | title = "\(Bundle.main.name) would like to access your \(permission)" 153 | message = "Please enable access to \(permission)." 154 | cancel = "Cancel" 155 | confirm = "Confirm" 156 | } 157 | 158 | fileprivate func confirmHandler(_ action: UIAlertAction) { 159 | permission.requestAuthorization(callbacks) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Source/PermissionButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionButton.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | open let permission: Permission 29 | 30 | /// The permission domain of the button. 31 | open var domain: PermissionType { return permission.type } 32 | 33 | /// The permission status of the button. 34 | open var status: PermissionStatus { return permission.status } 35 | 36 | fileprivate var titles: [UIControlState: [PermissionStatus: String]] = [:] 37 | fileprivate var attributedTitles: [UIControlState: [PermissionStatus: NSAttributedString]] = [:] 38 | fileprivate var titleColors: [UIControlState: [PermissionStatus: UIColor]] = [:] 39 | fileprivate var titleShadowColors: [UIControlState: [PermissionStatus: UIColor]] = [:] 40 | fileprivate var images: [UIControlState: [PermissionStatus: UIImage]] = [:] 41 | fileprivate var backgroundImages: [UIControlState: [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 | self.addTarget(self, action: .tapped, for: .touchUpInside) 73 | self.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: UIControlState = .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: UIControlState) { 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: UIControlState = .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: UIControlState = .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: UIControlState = .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: UIControlState) { 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: UIControlState = .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: UIControlState = .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: UIControlState = .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: UIControlState) { 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: UIControlState = .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: UIControlState = .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: UIControlState = .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: UIControlState) { 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: UIControlState = .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: UIControlState = .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: UIControlState = .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: UIControlState) { 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: UIControlState = .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: UIControlState = .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: UIControlState = .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: UIControlState) { 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: UIControlState = .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: UIControlState = .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 | internal extension PermissionButton { 458 | @objc func highlight(_ button: PermissionButton) { 459 | render(.highlighted) 460 | } 461 | 462 | @objc func tapped(_ button: PermissionButton) { 463 | permission.request { [weak self] status in 464 | self?.render() 465 | } 466 | } 467 | } 468 | 469 | private extension PermissionButton { 470 | func render(_ state: UIControlState = .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 | -------------------------------------------------------------------------------- /Source/PermissionSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionSet.swift 3 | // 4 | // Copyright (c) 2015-2016 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 PermissionSet { 26 | 27 | /// The permissions in the set. 28 | open let permissions: Set 29 | 30 | /// The delegate of the permission set. 31 | open weak var delegate: PermissionSetDelegate? 32 | 33 | /// The permission set status 34 | open var status: PermissionStatus { 35 | let statuses = permissions.map({ $0.status }) 36 | 37 | for status in statuses where status == .denied { 38 | return .denied 39 | } 40 | 41 | for status in statuses where status == .disabled { 42 | return .disabled 43 | } 44 | 45 | for status in statuses where status == .notDetermined { 46 | return .notDetermined 47 | } 48 | 49 | return .authorized 50 | } 51 | 52 | /** 53 | Creates and returns a new permission set containing the specified buttons. 54 | 55 | - parameter buttons: The buttons contained by the set. 56 | 57 | - returns: A newly created set. 58 | */ 59 | public convenience init(_ buttons: PermissionButton...) { 60 | self.init(buttons: buttons) 61 | } 62 | 63 | /** 64 | Creates and returns a new permission set containing the specified buttons. 65 | 66 | - parameter buttons: The buttons contained by the set. 67 | 68 | - returns: A newly created set. 69 | */ 70 | public convenience init(_ buttons: [PermissionButton]) { 71 | self.init(buttons: buttons) 72 | } 73 | 74 | /** 75 | Creates and returns a new permission set containing the specified buttons. 76 | 77 | - parameter permissions: The permissions contained by the set. 78 | 79 | - returns: A newly created set. 80 | */ 81 | public convenience init(_ permissions: Permission...) { 82 | self.init(permissions: permissions) 83 | } 84 | 85 | /** 86 | Creates and returns a new permission set containing the specified buttons. 87 | 88 | - parameter permissions: The permissions contained by the set. 89 | 90 | - returns: A newly created set. 91 | */ 92 | public convenience init(_ permissions: [Permission]) { 93 | self.init(permissions: permissions) 94 | } 95 | 96 | fileprivate convenience init(buttons: [PermissionButton]) { 97 | let permissions = buttons.map({ $0.permission }) 98 | 99 | self.init(permissions: permissions) 100 | } 101 | 102 | fileprivate init(permissions: [Permission]) { 103 | self.permissions = Set(permissions) 104 | self.permissions.forEach { $0.permissionSets.append(self) } 105 | } 106 | 107 | internal func willRequestPermission(_ permission: Permission) { 108 | delegate?.permissionSet(self, willRequestPermission: permission) 109 | } 110 | 111 | internal func didRequestPermission(_ permission: Permission) { 112 | delegate?.permissionSet(self, didRequestPermission: permission) 113 | } 114 | } 115 | 116 | extension PermissionSet: CustomStringConvertible { 117 | /// The textual representation of self. 118 | public var description: String { 119 | return [ 120 | "\(status): [", 121 | permissions.map{ "\t\($0)" }.joined(separator: ",\n"), 122 | "]" 123 | ].joined(separator: "\n") 124 | } 125 | } 126 | 127 | public protocol PermissionSetDelegate: class { 128 | /** 129 | Tells the delegate that the specified permission has been requested. 130 | 131 | - parameter permissionSet: The permission set containing the requested permission. 132 | - parameter permission: The requested permission. 133 | */ 134 | func permissionSet(_ permissionSet: PermissionSet, didRequestPermission permission: Permission) 135 | 136 | /** 137 | Tells the delegate that the specified permission will be requested. 138 | 139 | - parameter permissionSet: The permission set containing the requested permission. 140 | - parameter permission: The requested permission. 141 | */ 142 | func permissionSet(_ permissionSet: PermissionSet, willRequestPermission permission: Permission) 143 | } 144 | 145 | public extension PermissionSetDelegate { 146 | /** 147 | Tells the delegate that the specified permission has been requested. 148 | 149 | - parameter permissionSet: The permission set containing the requested permission. 150 | - parameter permission: The requested permission. 151 | */ 152 | func permissionSet(_ permissionSet: PermissionSet, didRequestPermission permission: Permission) {} 153 | 154 | /** 155 | Tells the delegate that the specified permission will be requested. 156 | 157 | - parameter permissionSet: The permission set containing the requested permission. 158 | - parameter permission: The requested permission. 159 | */ 160 | func permissionSet(_ permissionSet: PermissionSet, willRequestPermission permission: Permission) {} 161 | } 162 | -------------------------------------------------------------------------------- /Source/PermissionStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionStatus.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | public enum PermissionStatus: String { 26 | case authorized = "Authorized" 27 | case denied = "Denied" 28 | case disabled = "Disabled" 29 | case notDetermined = "Not Determined" 30 | 31 | internal init?(string: String?) { 32 | guard let string = string else { return nil } 33 | self.init(rawValue: string) 34 | } 35 | } 36 | 37 | extension PermissionStatus: CustomStringConvertible { 38 | /// The textual representation of self. 39 | public var description: String { 40 | return rawValue 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/PermissionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionType.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | public enum PermissionType { 26 | #if PERMISSION_CONTACTS 27 | @available(iOS 9.0, *) case contacts 28 | #endif 29 | 30 | #if PERMISSION_ADDRESS_BOOK 31 | case addressBook // Deprecated in iOS 9.0 32 | #endif 33 | 34 | #if PERMISSION_LOCATION 35 | case locationAlways 36 | case locationWhenInUse 37 | #endif 38 | 39 | #if PERMISSION_NOTIFICATIONS 40 | case notifications(UIUserNotificationSettings) 41 | #endif 42 | 43 | #if PERMISSION_MICROPHONE 44 | case microphone 45 | #endif 46 | 47 | #if PERMISSION_CAMERA 48 | case camera 49 | #endif 50 | 51 | #if PERMISSION_PHOTOS 52 | case photos 53 | #endif 54 | 55 | #if PERMISSION_REMINDERS 56 | case reminders 57 | #endif 58 | 59 | #if PERMISSION_EVENTS 60 | case events 61 | #endif 62 | 63 | #if PERMISSION_BLUETOOTH 64 | case bluetooth 65 | #endif 66 | 67 | #if PERMISSION_MOTION 68 | case motion 69 | #endif 70 | 71 | #if PERMISSION_SPEECH_RECOGNIZER 72 | @available(iOS 10.0, *) case speechRecognizer 73 | #endif 74 | 75 | #if PERMISSION_MEDIA_LIBRARY 76 | @available(iOS 9.3, *) case mediaLibrary 77 | #endif 78 | 79 | #if PERMISSION_SIRI 80 | @available(iOS 10.0, *) case siri 81 | #endif 82 | } 83 | 84 | extension PermissionType: CustomStringConvertible { 85 | public var description: String { 86 | #if PERMISSION_CONTACTS 87 | if case .contacts = self { return "Contacts" } 88 | #endif 89 | 90 | #if PERMISSION_ADDRESS_BOOK 91 | if case .addressBook = self { return "Address Book" } 92 | #endif 93 | 94 | #if PERMISSION_LOCATION 95 | if case .locationAlways = self { return "Location" } 96 | if case .locationWhenInUse = self { return "Location" } 97 | #endif 98 | 99 | #if PERMISSION_NOTIFICATIONS 100 | if case .notifications = self { return "Notifications" } 101 | #endif 102 | 103 | #if PERMISSION_MICROPHONE 104 | if case .microphone = self { return "Microphone" } 105 | #endif 106 | 107 | #if PERMISSION_CAMERA 108 | if case .camera = self { return "Camera" } 109 | #endif 110 | 111 | #if PERMISSION_PHOTOS 112 | if case .photos = self { return "Photos" } 113 | #endif 114 | 115 | #if PERMISSION_REMINDERS 116 | if case .reminders = self { return "Reminders" } 117 | #endif 118 | 119 | #if PERMISSION_EVENTS 120 | if case .events = self { return "Events" } 121 | #endif 122 | 123 | #if PERMISSION_BLUETOOTH 124 | if case .bluetooth = self { return "Bluetooth" } 125 | #endif 126 | 127 | #if PERMISSION_MOTION 128 | if case .motion = self { return "Motion" } 129 | #endif 130 | 131 | #if PERMISSION_SPEECH_RECOGNIZER 132 | if case .speechRecognizer = self { return "Speech Recognizer" } 133 | #endif 134 | 135 | #if PERMISSION_SIRI 136 | if case .siri = self { return "SiriKit" } 137 | #endif 138 | 139 | #if PERMISSION_MEDIA_LIBRARY 140 | if case .mediaLibrary = self { return "Media Library" } 141 | #endif 142 | 143 | fatalError() 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Source/PermissionTypes/AddressBook.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddressBook.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_ADDRESS_BOOK 26 | import AddressBook 27 | 28 | internal extension Permission { 29 | var statusAddressBook: PermissionStatus { 30 | let status = ABAddressBookGetAuthorizationStatus() 31 | 32 | switch status { 33 | case .authorized: return .authorized 34 | case .restricted, .denied: return .denied 35 | case .notDetermined: return .notDetermined 36 | } 37 | } 38 | 39 | func requestAddressBook(_ callback: @escaping Callback) { 40 | ABAddressBookRequestAccessWithCompletion(nil) { _, _ in 41 | callback(self.statusAddressBook) 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Bluetooth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bluetooth.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_BLUETOOTH 26 | import CoreBluetooth 27 | 28 | internal let BluetoothManager = CBPeripheralManager( 29 | delegate: Permission.bluetooth, 30 | queue: nil, 31 | options: [CBPeripheralManagerOptionShowPowerAlertKey: false] 32 | ) 33 | 34 | extension Permission { 35 | var statusBluetooth: PermissionStatus { 36 | switch CBPeripheralManager.authorizationStatus() { 37 | case .restricted: return .disabled 38 | case .denied: return .denied 39 | case .notDetermined, .authorized: break 40 | } 41 | 42 | guard UserDefaults.standard.stateBluetoothManagerDetermined else { return .notDetermined } 43 | 44 | switch BluetoothManager.state { 45 | case .unsupported, .poweredOff: return .disabled 46 | case .unauthorized: return .denied 47 | case .poweredOn: return .authorized 48 | case .resetting, .unknown: 49 | return UserDefaults.standard.statusBluetooth ?? .notDetermined 50 | } 51 | } 52 | 53 | func requestBluetooth(_ callback: Callback?) { 54 | UserDefaults.standard.requestedBluetooth = true 55 | 56 | BluetoothManager.request(self) 57 | } 58 | } 59 | 60 | extension Permission: CBPeripheralManagerDelegate { 61 | public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { 62 | UserDefaults.standard.stateBluetoothManagerDetermined = true 63 | UserDefaults.standard.statusBluetooth = statusBluetooth 64 | 65 | guard UserDefaults.standard.requestedBluetooth else { return } 66 | 67 | callback?(statusBluetooth) 68 | 69 | UserDefaults.standard.requestedBluetooth = false 70 | } 71 | } 72 | 73 | extension CBPeripheralManager { 74 | func request(_ permission: Permission) { 75 | guard case .poweredOn = state else { return } 76 | 77 | startAdvertising(nil) 78 | stopAdvertising() 79 | } 80 | } 81 | #endif 82 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Camera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Camera.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_CAMERA 26 | import AVFoundation 27 | 28 | internal extension Permission { 29 | var statusCamera: PermissionStatus { 30 | let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) 31 | 32 | switch status { 33 | case .authorized: return .authorized 34 | case .restricted, .denied: return .denied 35 | case .notDetermined: return .notDetermined 36 | } 37 | } 38 | 39 | func requestCamera(_ callback: @escaping Callback) { 40 | guard let _ = Bundle.main.object(forInfoDictionaryKey: .cameraUsageDescription) else { 41 | print("WARNING: \(String.cameraUsageDescription) not found in Info.plist") 42 | return 43 | } 44 | 45 | AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { _ in 46 | callback(self.statusCamera) 47 | } 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Contacts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contacts.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_CONTACTS 26 | import Contacts 27 | 28 | internal extension Permission { 29 | var statusContacts: PermissionStatus { 30 | guard #available(iOS 9.0, *) else { fatalError() } 31 | 32 | let status = CNContactStore.authorizationStatus(for: .contacts) 33 | 34 | switch status { 35 | case .authorized: return .authorized 36 | case .restricted, .denied: return .denied 37 | case .notDetermined: return .notDetermined 38 | } 39 | } 40 | 41 | func requestContacts(_ callback: @escaping Callback) { 42 | guard #available(iOS 9.0, *) else { fatalError() } 43 | 44 | CNContactStore().requestAccess(for: .contacts) { _, _ in 45 | callback(self.statusContacts) 46 | } 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Events.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Events.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_EVENTS 26 | import EventKit 27 | 28 | internal extension Permission { 29 | var statusEvents: PermissionStatus { 30 | let status = EKEventStore.authorizationStatus(for: .event) 31 | 32 | switch status { 33 | case .authorized: return .authorized 34 | case .restricted, .denied: return .denied 35 | case .notDetermined: return .notDetermined 36 | } 37 | } 38 | 39 | func requestEvents(_ callback: @escaping Callback) { 40 | EKEventStore().requestAccess(to: .event) { _,_ in 41 | callback(self.statusEvents) 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Location.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Location.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_LOCATION 26 | import CoreLocation 27 | 28 | internal let LocationManager = CLLocationManager() 29 | 30 | private var requestedLocation = false 31 | private var triggerCallbacks = false 32 | 33 | extension Permission: CLLocationManagerDelegate { 34 | public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 35 | switch (requestedLocation, triggerCallbacks) { 36 | case (true, false): 37 | triggerCallbacks = true 38 | case (true, true): 39 | requestedLocation = false 40 | triggerCallbacks = false 41 | callbacks(self.status) 42 | default: 43 | break 44 | } 45 | } 46 | } 47 | 48 | 49 | extension CLLocationManager { 50 | func request(_ permission: Permission) { 51 | delegate = permission 52 | 53 | requestedLocation = true 54 | 55 | if case .locationAlways = permission.type { 56 | requestAlwaysAuthorization() 57 | return 58 | } 59 | 60 | if case .locationWhenInUse = permission.type { 61 | requestWhenInUseAuthorization() 62 | return 63 | } 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /Source/PermissionTypes/LocationAlways.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationAlways.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_LOCATION 26 | import CoreLocation 27 | 28 | internal extension Permission { 29 | var statusLocationAlways: PermissionStatus { 30 | guard CLLocationManager.locationServicesEnabled() else { return .disabled } 31 | 32 | let status = CLLocationManager.authorizationStatus() 33 | 34 | switch status { 35 | case .authorizedAlways: return .authorized 36 | case .authorizedWhenInUse: 37 | return UserDefaults.standard.requestedLocationAlwaysWithWhenInUse ? .denied : .notDetermined 38 | case .notDetermined: return .notDetermined 39 | case .restricted, .denied: return .denied 40 | } 41 | } 42 | 43 | func requestLocationAlways(_ callback: Callback) { 44 | guard let _ = Foundation.Bundle.main.object(forInfoDictionaryKey: .locationAlwaysUsageDescription) else { 45 | print("WARNING: \(String.locationAlwaysUsageDescription) not found in Info.plist") 46 | return 47 | } 48 | 49 | if CLLocationManager.authorizationStatus() == .authorizedWhenInUse { 50 | UserDefaults.standard.requestedLocationAlwaysWithWhenInUse = true 51 | } 52 | 53 | LocationManager.request(self) 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /Source/PermissionTypes/LocationWhenInUse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationWhenInUse.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_LOCATION 26 | import CoreLocation 27 | 28 | internal extension Permission { 29 | var statusLocationWhenInUse: PermissionStatus { 30 | guard CLLocationManager.locationServicesEnabled() else { return .disabled } 31 | 32 | let status = CLLocationManager.authorizationStatus() 33 | 34 | switch status { 35 | case .authorizedWhenInUse, .authorizedAlways: return .authorized 36 | case .restricted, .denied: return .denied 37 | case .notDetermined: return .notDetermined 38 | } 39 | } 40 | 41 | func requestLocationWhenInUse(_ callback: Callback) { 42 | guard let _ = Foundation.Bundle.main.object(forInfoDictionaryKey: .locationWhenInUseUsageDescription) else { 43 | print("WARNING: \(String.locationWhenInUseUsageDescription) not found in Info.plist") 44 | return 45 | } 46 | 47 | LocationManager.request(self) 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Source/PermissionTypes/MediaLibrary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediaLibrary.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_MEDIA_LIBRARY 26 | import MediaPlayer 27 | 28 | internal extension Permission { 29 | var statusMediaLibrary: PermissionStatus { 30 | guard #available(iOS 9.3, *) else { fatalError() } 31 | 32 | let status = MPMediaLibrary.authorizationStatus() 33 | 34 | switch status { 35 | case .authorized: return .authorized 36 | case .restricted, .denied: return .denied 37 | case .notDetermined: return .notDetermined 38 | } 39 | } 40 | 41 | func requestMediaLibrary(_ callback: @escaping Callback) { 42 | guard #available(iOS 9.3, *) else { fatalError() } 43 | 44 | guard let _ = Bundle.main.object(forInfoDictionaryKey: .mediaLibraryUsageDescription) else { 45 | print("WARNING: \(String.mediaLibraryUsageDescription) not found in Info.plist") 46 | return 47 | } 48 | 49 | MPMediaLibrary.requestAuthorization { _ in 50 | callback(self.statusMediaLibrary) 51 | } 52 | } 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Microphone.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Microphone.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_MICROPHONE 26 | import AVFoundation 27 | 28 | internal extension Permission { 29 | var statusMicrophone: PermissionStatus { 30 | let status = AVAudioSession.sharedInstance().recordPermission() 31 | 32 | switch status { 33 | case AVAudioSessionRecordPermission.denied: return .denied 34 | case AVAudioSessionRecordPermission.granted: return .authorized 35 | default: return .notDetermined 36 | } 37 | } 38 | 39 | func requestMicrophone(_ callback: @escaping Callback) { 40 | AVAudioSession.sharedInstance().requestRecordPermission { _ in 41 | callback(self.statusMicrophone) 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Motion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Motion.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_MOTION 26 | import CoreMotion 27 | 28 | private let MotionManager = CMMotionActivityManager() 29 | 30 | extension Permission { 31 | var statusMotion: PermissionStatus { 32 | if UserDefaults.standard.requestedMotion { 33 | return synchronousStatusMotion 34 | } 35 | 36 | return .notDetermined 37 | } 38 | 39 | func requestMotion(_ callback: Callback?) { 40 | UserDefaults.standard.requestedMotion = true 41 | 42 | let now = Date() 43 | 44 | MotionManager.queryActivityStarting(from: now, to: now, to: OperationQueue.main) { activities, error in 45 | let status: PermissionStatus 46 | 47 | if let error = error , error._code == Int(CMErrorMotionActivityNotAuthorized.rawValue) { 48 | status = .denied 49 | } else { 50 | status = .authorized 51 | } 52 | 53 | MotionManager.stopActivityUpdates() 54 | 55 | callback?(status) 56 | } 57 | } 58 | 59 | fileprivate var synchronousStatusMotion: PermissionStatus { 60 | let semaphore = DispatchSemaphore(value: 0) 61 | 62 | var status: PermissionStatus = .notDetermined 63 | 64 | let now = Date() 65 | 66 | MotionManager.queryActivityStarting(from: now, to: now, to: OperationQueue(.background)) { activities, error in 67 | if let error = error , error._code == Int(CMErrorMotionActivityNotAuthorized.rawValue) { 68 | status = .denied 69 | } else { 70 | status = .authorized 71 | } 72 | 73 | MotionManager.stopActivityUpdates() 74 | 75 | semaphore.signal() 76 | } 77 | 78 | 79 | _ = semaphore.wait(timeout: DispatchTime.distantFuture) 80 | 81 | return status 82 | } 83 | } 84 | #endif 85 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_NOTIFICATIONS 26 | internal extension Permission { 27 | var statusNotifications: PermissionStatus { 28 | if UIApplication.shared.currentUserNotificationSettings?.types.isEmpty == false { 29 | return .authorized 30 | } 31 | 32 | return UserDefaults.standard.requestedNotifications ? .denied : .notDetermined 33 | } 34 | 35 | func requestNotifications(_ callback: Callback) { 36 | guard case .notifications(let settings) = type else { fatalError() } 37 | 38 | NotificationCenter.default.addObserver(self, selector: #selector(requestingNotifications), name: .UIApplicationWillResignActive) 39 | 40 | UIApplication.shared.registerUserNotificationSettings(settings) 41 | } 42 | 43 | @objc func requestingNotifications() { 44 | NotificationCenter.default.removeObserver(self, name: .UIApplicationWillResignActive) 45 | NotificationCenter.default.addObserver(self, selector: #selector(finishedRequestingNotifications), name: .UIApplicationDidBecomeActive) 46 | } 47 | 48 | @objc func finishedRequestingNotifications() { 49 | NotificationCenter.default.removeObserver(self, name: .UIApplicationWillResignActive) 50 | NotificationCenter.default.removeObserver(self, name: .UIApplicationDidBecomeActive) 51 | 52 | UserDefaults.standard.requestedNotifications = true 53 | 54 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { 55 | self.callbacks(self.statusNotifications) 56 | } 57 | } 58 | } 59 | #endif 60 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Photos.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Photos.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_PHOTOS 26 | import Photos 27 | 28 | internal extension Permission { 29 | var statusPhotos: PermissionStatus { 30 | let status = PHPhotoLibrary.authorizationStatus() 31 | 32 | switch status { 33 | case .authorized: return .authorized 34 | case .denied, .restricted: return .denied 35 | case .notDetermined: return .notDetermined 36 | } 37 | } 38 | 39 | func requestPhotos(_ callback: @escaping Callback) { 40 | guard let _ = Bundle.main.object(forInfoDictionaryKey: .photoLibraryUsageDescription) else { 41 | print("WARNING: \(String.photoLibraryUsageDescription) not found in Info.plist") 42 | return 43 | } 44 | 45 | PHPhotoLibrary.requestAuthorization { _ in 46 | callback(self.statusPhotos) 47 | } 48 | } 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Reminders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reminders.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_REMINDERS 26 | import EventKit 27 | 28 | internal extension Permission { 29 | var statusReminders: PermissionStatus { 30 | let status = EKEventStore.authorizationStatus(for: .reminder) 31 | 32 | switch status { 33 | case .authorized: return .authorized 34 | case .restricted, .denied: return .denied 35 | case .notDetermined: return .notDetermined 36 | } 37 | } 38 | 39 | func requestReminders(_ callback: @escaping Callback) { 40 | EKEventStore().requestAccess(to: .reminder) { _,_ in 41 | callback(self.statusReminders) 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Source/PermissionTypes/Siri.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Siri.swift 3 | // 4 | // Copyright (c) 2015-2017 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 | #if PERMISSION_SIRI 26 | import Intents 27 | 28 | internal extension Permission { 29 | var statusSiri: PermissionStatus { 30 | guard #available(iOS 10.0, *) else { fatalError() } 31 | let status = INPreferences.siriAuthorizationStatus() 32 | switch status { 33 | case .authorized: return .authorized 34 | case .restricted, .denied: return .denied 35 | case .notDetermined: return .notDetermined 36 | } 37 | } 38 | func requestSiri(_ callback: @escaping Callback) { 39 | guard #available(iOS 10.0, *) else { fatalError() } 40 | guard let _ = Bundle.main.object(forInfoDictionaryKey: .siriUsageDescription) else { 41 | print("WARNING: \(String.siriUsageDescription) not found in Info.plist") 42 | return 43 | } 44 | INPreferences.requestSiriAuthorization({ (status) in 45 | callback(self.statusSiri) 46 | }) 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Source/PermissionTypes/SpeechRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpeechRecognizer.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | #if PERMISSION_SPEECH_RECOGNIZER 26 | import Speech 27 | 28 | internal extension Permission { 29 | var statusSpeechRecognizer: PermissionStatus { 30 | guard #available(iOS 10.0, *) else { fatalError() } 31 | 32 | let status = SFSpeechRecognizer.authorizationStatus() 33 | 34 | switch status { 35 | case .authorized: return .authorized 36 | case .restricted, .denied: return .denied 37 | case .notDetermined: return .notDetermined 38 | } 39 | } 40 | 41 | func requestSpeechRecognizer(_ callback: @escaping Callback) { 42 | guard #available(iOS 10.0, *) else { fatalError() } 43 | 44 | guard let _ = Bundle.main.object(forInfoDictionaryKey: .microphoneUsageDescription) else { 45 | print("WARNING: \(String.microphoneUsageDescription) not found in Info.plist") 46 | return 47 | } 48 | 49 | guard let _ = Bundle.main.object(forInfoDictionaryKey: .speechRecognitionUsageDescription) else { 50 | print("WARNING: \(String.speechRecognitionUsageDescription) not found in Info.plist") 51 | return 52 | } 53 | 54 | SFSpeechRecognizer.requestAuthorization { _ in 55 | callback(self.statusSpeechRecognizer) 56 | } 57 | } 58 | } 59 | #endif 60 | -------------------------------------------------------------------------------- /Source/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/Supporting Files/Permission.h: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.h 3 | // 4 | // Copyright (c) 2015-2016 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 | #import 26 | 27 | FOUNDATION_EXPORT double PermissionVersionNumber; 28 | FOUNDATION_EXPORT const unsigned char PermissionVersionString[]; -------------------------------------------------------------------------------- /Source/Supporting Files/PermissionFlags.xcconfig: -------------------------------------------------------------------------------- 1 | // This hook allows users of the framework to provide build flags without modifying any files within the Permission repository. See README.md for instructions. 2 | #include? "../../PermissionConfiguration.xcconfig" 3 | 4 | // This secondary hook allows to provide a configuration two levels further up the directory hierarchy. It exists primarily for Carthage support to allow for config files outside of auto-generated directories that may not be under version control. 5 | // Carthage directories are typically structured: 6 | // "Carthage/Build/iOS/Permission" 7 | #include? "../../../../../PermissionConfiguration.xcconfig" 8 | -------------------------------------------------------------------------------- /Source/Supporting Files/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | extension UIApplication { 26 | fileprivate var topViewController: UIViewController? { 27 | var vc = delegate?.window??.rootViewController 28 | 29 | while let presentedVC = vc?.presentedViewController { 30 | vc = presentedVC 31 | } 32 | 33 | return vc 34 | } 35 | 36 | internal func presentViewController(_ viewController: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) { 37 | topViewController?.present(viewController, animated: animated, completion: completion) 38 | } 39 | } 40 | 41 | extension Bundle { 42 | var name: String { 43 | return object(forInfoDictionaryKey: "CFBundleName") as? String ?? "" 44 | } 45 | } 46 | 47 | extension UIControlState: Hashable { 48 | public var hashValue: Int { return Int(rawValue) } 49 | } 50 | 51 | internal extension String { 52 | static let locationWhenInUseUsageDescription = "NSLocationWhenInUseUsageDescription" 53 | static let locationAlwaysUsageDescription = "NSLocationAlwaysUsageDescription" 54 | static let microphoneUsageDescription = "NSMicrophoneUsageDescription" 55 | static let speechRecognitionUsageDescription = "NSSpeechRecognitionUsageDescription" 56 | static let photoLibraryUsageDescription = "NSPhotoLibraryUsageDescription" 57 | static let cameraUsageDescription = "NSCameraUsageDescription" 58 | static let mediaLibraryUsageDescription = "NSAppleMusicUsageDescription" 59 | static let siriUsageDescription = "NSSiriUsageDescription" 60 | 61 | 62 | static let requestedNotifications = "permission.requestedNotifications" 63 | static let requestedLocationAlwaysWithWhenInUse = "permission.requestedLocationAlwaysWithWhenInUse" 64 | static let requestedMotion = "permission.requestedMotion" 65 | static let requestedBluetooth = "permission.requestedBluetooth" 66 | static let statusBluetooth = "permission.statusBluetooth" 67 | static let stateBluetoothManagerDetermined = "permission.stateBluetoothManagerDetermined" 68 | } 69 | 70 | internal extension Selector { 71 | static let tapped = #selector(PermissionButton.tapped(_:)) 72 | static let highlight = #selector(PermissionButton.highlight(_:)) 73 | static let settingsHandler = #selector(DeniedAlert.settingsHandler) 74 | } 75 | 76 | extension UserDefaults { 77 | var requestedLocationAlwaysWithWhenInUse: Bool { 78 | get { return bool(forKey: .requestedLocationAlwaysWithWhenInUse) } 79 | set { set(newValue, forKey: .requestedLocationAlwaysWithWhenInUse) } 80 | } 81 | 82 | var requestedNotifications: Bool { 83 | get { return bool(forKey: .requestedNotifications) } 84 | set { set(newValue, forKey: .requestedNotifications) } 85 | } 86 | 87 | var requestedMotion: Bool { 88 | get { return bool(forKey: .requestedMotion) } 89 | set { set(newValue, forKey: .requestedMotion) } 90 | } 91 | 92 | var requestedBluetooth: Bool { 93 | get { return bool(forKey: .requestedBluetooth) } 94 | set { set(newValue, forKey: .requestedBluetooth) } 95 | } 96 | 97 | var statusBluetooth: PermissionStatus? { 98 | get { return PermissionStatus(string: string(forKey: .statusBluetooth)) } 99 | set { set(newValue?.rawValue, forKey: .statusBluetooth) } 100 | } 101 | 102 | var stateBluetoothManagerDetermined: Bool { 103 | get { return bool(forKey: .stateBluetoothManagerDetermined) } 104 | set { set(newValue, forKey: .stateBluetoothManagerDetermined) } 105 | } 106 | } 107 | 108 | extension OperationQueue { 109 | convenience init(_ qualityOfService: QualityOfService) { 110 | self.init() 111 | self.qualityOfService = qualityOfService 112 | } 113 | } 114 | 115 | internal extension NotificationCenter { 116 | func addObserver(_ observer: AnyObject, selector: Selector, name: NSNotification.Name?) { 117 | addObserver(observer, selector: selector, name: name!, object: nil) 118 | } 119 | 120 | func removeObserver(_ observer: AnyObject, name: NSNotification.Name?) { 121 | removeObserver(observer, name: name, object: nil) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Tests/PermissionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionTests.swift 3 | // 4 | // Copyright (c) 2015-2016 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 | import XCTest 26 | 27 | class PermissionTests: XCTestCase { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Tests/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | --------------------------------------------------------------------------------