├── .gitignore ├── Demo ├── Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Demo.xcscheme ├── Demo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── ChineseAlertContent.swift │ ├── Demo.entitlements │ ├── Info.plist │ ├── Permission.swift │ └── ViewController.swift ├── Podfile ├── Podfile.lock └── Pods │ ├── Bits │ ├── LICENSE │ ├── README.md │ └── Sources │ │ └── Bits │ │ ├── Aliases.swift │ │ ├── Base64Encoder.swift │ │ ├── Byte+Alphabet.swift │ │ ├── Byte+ControlCharacters.swift │ │ ├── Byte+Convenience.swift │ │ ├── Byte+PatternMatching.swift │ │ ├── Byte+Random.swift │ │ ├── Byte+UTF8Numbers.swift │ │ ├── ByteSequence+Conversions.swift │ │ ├── Bytes+Base64.swift │ │ ├── Bytes+Hex.swift │ │ ├── Bytes+Percent.swift │ │ ├── BytesConvertible.swift │ │ ├── Data+BytesConvertible.swift │ │ ├── HexEncoder.swift │ │ ├── Operators.swift │ │ ├── String+BytesConvertible.swift │ │ ├── UnsignedInteger+BytesConvertible.swift │ │ └── UnsignedInteger+Shifting.swift │ ├── Core │ ├── LICENSE │ ├── README.md │ └── Sources │ │ ├── Core │ │ ├── Array.swift │ │ ├── Bits.swift │ │ ├── Cache.swift │ │ ├── Collection+Safe.swift │ │ ├── DataFile.swift │ │ ├── Dispatch.swift │ │ ├── DispatchTime+Utilities.swift │ │ ├── EmptyInitializable.swift │ │ ├── Exports.swift │ │ ├── Extendable.swift │ │ ├── FileProtocol.swift │ │ ├── Int+Hex.swift │ │ ├── Lock.swift │ │ ├── Portal.swift │ │ ├── RFC1123.swift │ │ ├── Result.swift │ │ ├── Semaphore.swift │ │ ├── Sequence.swift │ │ ├── StaticDataBuffer.swift │ │ ├── String+CaseInsensitiveCompare.swift │ │ ├── String+Polymorphic.swift │ │ ├── String.swift │ │ └── WorkingDirectory.swift │ │ └── libc │ │ └── libc.swift │ ├── Local Podspecs │ ├── Permission.podspec.json │ ├── PermissionKit.podspec.json │ └── Spring.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ └── project.pbxproj │ └── Target Support Files │ ├── PermissionKit │ ├── PermissionKit-Info.plist │ ├── PermissionKit-dummy.m │ ├── PermissionKit-prefix.pch │ ├── PermissionKit-umbrella.h │ ├── PermissionKit.debug.xcconfig │ ├── PermissionKit.modulemap │ ├── PermissionKit.release.xcconfig │ └── ResourceBundle-Privacy-PermissionKit-Info.plist │ └── Pods-Demo │ ├── Pods-Demo-Info.plist │ ├── Pods-Demo-acknowledgements.markdown │ ├── Pods-Demo-acknowledgements.plist │ ├── Pods-Demo-dummy.m │ ├── Pods-Demo-frameworks-Debug-input-files.xcfilelist │ ├── Pods-Demo-frameworks-Debug-output-files.xcfilelist │ ├── Pods-Demo-frameworks-Release-input-files.xcfilelist │ ├── Pods-Demo-frameworks-Release-output-files.xcfilelist │ ├── Pods-Demo-frameworks.sh │ ├── Pods-Demo-umbrella.h │ ├── Pods-Demo.debug.xcconfig │ ├── Pods-Demo.modulemap │ └── Pods-Demo.release.xcconfig ├── LICENSE ├── PermissionKit.podspec ├── PermissionKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── PermissionKit.xcscheme ├── README.md ├── README_CN.md └── Sources ├── Alert ├── DefaultAlertContent.swift ├── Provider+Alert.swift └── SystemAlert.swift ├── Core ├── Protocol.swift └── Provider.swift ├── Info.plist ├── Managers ├── Permission.Bluetooth.swift ├── Permission.Camera.swift ├── Permission.Contacts.swift ├── Permission.Event.swift ├── Permission.Location.swift ├── Permission.Media.swift ├── Permission.Motion.swift ├── Permission.Notification.swift ├── Permission.Photos.swift ├── Permission.Siri.swift ├── Permission.Speech.swift └── Permission.Tracking.swift ├── PermissionKit.h └── PrivacyInfo.xcprivacy /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Demo/Demo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Demo 4 | // 5 | // Created by 李响 on 2019/3/30. 6 | // Copyright © 2019 swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Demo/Demo/ChineseAlertContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChineseAlertContent.swift 3 | // Demo 4 | // 5 | // Created by 李响 on 2019/6/4. 6 | // Copyright © 2019 swift. All rights reserved. 7 | // 8 | 9 | import PermissionKit 10 | 11 | public struct ChineseAlertContent: PermissionAlertContentSource { 12 | 13 | public init() { } 14 | 15 | public func title(_ status: AlertStatus) -> String { 16 | switch status { 17 | case .prepare(let name): 18 | return "\(Bundle.main.appName) 想要使用你的 \(name) 权限" 19 | 20 | case .denied(let name): 21 | return "\(name) 的权限被拒绝" 22 | 23 | case .disabled(let name): 24 | return "\(name) 已被停用" 25 | } 26 | } 27 | 28 | public func message(_ status: AlertStatus) -> String { 29 | switch status { 30 | case .prepare(let name): 31 | return "请您开启 \(name) 权限" 32 | 33 | case .denied(let name): 34 | return "请开启设置APP中的 \(name) 权限" 35 | 36 | case .disabled(let name): 37 | return "请开启设置APP中的 \(name) 权限" 38 | } 39 | } 40 | 41 | public func cancelAction(_ status: AlertStatus) -> String { 42 | switch status { 43 | case .prepare: 44 | return "取消" 45 | 46 | case .denied: 47 | return "取消" 48 | 49 | case .disabled: 50 | return "好的" 51 | } 52 | } 53 | 54 | public func confirmAction(_ status: AlertStatus) -> String { 55 | switch status { 56 | case .prepare: 57 | return "确定" 58 | 59 | case .denied: 60 | return "设置" 61 | 62 | case .disabled: 63 | return "" 64 | } 65 | } 66 | } 67 | 68 | fileprivate extension Bundle { 69 | 70 | var appName: String { 71 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Demo/Demo/Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.siri 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | PermissionKit 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppleMusicUsageDescription 26 | 媒体 27 | NSCalendarsUsageDescription 28 | 日历 29 | NSCameraUsageDescription 30 | 拍摄相片 31 | NSContactsUsageDescription 32 | 联系人 33 | NSLocationAlwaysAndWhenInUseUsageDescription 34 | 用于显示您所在的城市名称 35 | NSLocationAlwaysUsageDescription 36 | 用于显示您所在的城市名称 37 | NSLocationUsageDescription 38 | 用于显示您所在的城市名称 39 | NSLocationWhenInUseUsageDescription 40 | 用于显示您所在的城市名称 41 | NSMicrophoneUsageDescription 42 | 麦克风 43 | NSMotionUsageDescription 44 | 运动 45 | NSPhotoLibraryAddUsageDescription 46 | 保存图片 47 | NSPhotoLibraryUsageDescription 48 | 访问相册 49 | NSRemindersUsageDescription 50 | 提醒 51 | NSSiriUsageDescription 52 | Siri 53 | NSSpeechRecognitionUsageDescription 54 | 语音 55 | UILaunchStoryboardName 56 | LaunchScreen 57 | UIMainStoryboardFile 58 | Main 59 | UIRequiredDeviceCapabilities 60 | 61 | armv7 62 | 63 | UISupportedInterfaceOrientations 64 | 65 | UIInterfaceOrientationPortrait 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | UISupportedInterfaceOrientations~ipad 70 | 71 | UIInterfaceOrientationPortrait 72 | UIInterfaceOrientationPortraitUpsideDown 73 | UIInterfaceOrientationLandscapeLeft 74 | UIInterfaceOrientationLandscapeRight 75 | 76 | NSUserTrackingUsageDescription 77 | 我们需要您的广告追踪权限来追踪广告 78 | 79 | 80 | -------------------------------------------------------------------------------- /Demo/Demo/Permission.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.swift 3 | // Demo 4 | // 5 | // Created by 李响 on 2019/6/5. 6 | // Copyright © 2019 swift. All rights reserved. 7 | // 8 | 9 | import PermissionKit 10 | 11 | enum Permission { 12 | 13 | enum Mode { 14 | case camera 15 | case photos(Provider.PhotosType) 16 | case calendar 17 | case reminder 18 | case contacts 19 | case speech 20 | case motion 21 | case media 22 | case siri 23 | case microphone 24 | case location(Provider.LocationType) 25 | case notification(Provider.NotificationOptions) 26 | case tracking 27 | 28 | var provider: Provider { 29 | let mode: Provider 30 | switch self { 31 | case .camera: 32 | mode = Provider.camera 33 | mode.alias = { "相机" } 34 | 35 | case .photos(let value): 36 | mode = Provider.photos(value) 37 | mode.alias = { 38 | switch value { 39 | case .addOnly: 40 | return "相册添加" 41 | 42 | case .readWrite: 43 | return "相册读写" 44 | } 45 | } 46 | 47 | case .calendar: 48 | mode = Provider.calendar 49 | mode.alias = { "カレンダー" } 50 | 51 | case .reminder: 52 | mode = Provider.reminder 53 | mode.alias = { "提醒" } 54 | 55 | case .contacts: 56 | mode = Provider.contacts 57 | mode.alias = { "주소록" } 58 | 59 | case .speech: 60 | mode = Provider.speech 61 | mode.alias = { "语音" } 62 | 63 | case .motion: 64 | mode = Provider.motion 65 | mode.alias = { "动作" } 66 | 67 | case .media: 68 | mode = Provider.media 69 | mode.alias = { "媒体库" } 70 | 71 | case .siri: 72 | mode = Provider.siri 73 | mode.alias = { "Siri" } 74 | 75 | case .microphone: 76 | mode = Provider.microphone 77 | mode.alias = { "麦克风" } 78 | 79 | case .location(let value): 80 | mode = Provider.location(value) 81 | mode.alias = { "定位" } 82 | 83 | case .notification(let value): 84 | mode = Provider.notification(value) 85 | mode.alias = { "通知" } 86 | 87 | case .tracking: 88 | mode = Provider.tracking 89 | mode.alias = { "跟踪" } 90 | } 91 | return mode 92 | } 93 | } 94 | 95 | public static func isAuthorized(_ mode: Mode) -> Bool { 96 | return mode.provider.isAuthorized 97 | } 98 | 99 | public static func request(_ mode: Mode, with сompletion: @escaping (Bool) -> Void) { 100 | let alert = SystemAlert(ChineseAlertContent()) 101 | mode.provider.request(alert, with: сompletion) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Demo/Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Demo 4 | // 5 | // Created by 李响 on 2019/3/30. 6 | // Copyright © 2019 swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | 15 | private var list: [String] = [ 16 | "相机权限", 17 | "相册权限 (添加)", 18 | "相册权限 (读写)", 19 | "日历权限", 20 | "提醒权限", 21 | "联系人权限", 22 | "语音权限", 23 | "动作权限", 24 | "媒体权限", 25 | "Siri权限", 26 | "麦克风权限", 27 | "通知权限", 28 | "位置权限", 29 | "跟踪权限" 30 | ] 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | setup() 36 | } 37 | 38 | private func setup() { 39 | tableView.delegate = self 40 | tableView.dataSource = self 41 | } 42 | } 43 | 44 | extension ViewController: UITableViewDelegate { 45 | 46 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 47 | return 50 48 | } 49 | } 50 | 51 | extension ViewController: UITableViewDataSource { 52 | 53 | func numberOfSections(in tableView: UITableView) -> Int { 54 | return 1 55 | } 56 | 57 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | return list.count 59 | } 60 | 61 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 62 | let cell = tableView.dequeueReusableCell( 63 | withIdentifier: "cell", 64 | for: indexPath 65 | ) 66 | cell.textLabel?.text = list[indexPath.row] 67 | return cell 68 | } 69 | 70 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 71 | tableView.deselectRow(at: indexPath, animated: true) 72 | let mode: Permission.Mode 73 | switch indexPath.row { 74 | case 0: // 相机权限 75 | mode = .camera 76 | 77 | case 1: // 相册权限 (添加) 78 | mode = .photos(.addOnly) 79 | 80 | case 2: // 相册权限 (读写) 81 | mode = .photos(.readWrite) 82 | 83 | case 3: // 日历权限 84 | mode = .calendar 85 | 86 | case 4: // 提醒权限 87 | mode = .reminder 88 | 89 | case 5: // 联系人权限 90 | mode = .contacts 91 | 92 | case 6: // 语音权限 93 | mode = .speech 94 | 95 | case 7: // 动作权限 96 | mode = .motion 97 | 98 | case 8: // 媒体权限 99 | mode = .media 100 | 101 | case 9: // Siri权限 102 | mode = .siri 103 | 104 | case 10: // 麦克风权限 105 | mode = .microphone 106 | 107 | case 11: // 通知权限 108 | mode = .notification([.alert, .badge, .sound, .provisional]) 109 | 110 | case 12: // 位置权限 111 | mode = .location(.whenInUse) 112 | 113 | case 13: // 跟踪权限 114 | mode = .tracking 115 | 116 | default: 117 | mode = .camera 118 | } 119 | 120 | print("isAuthorized: \(Permission.isAuthorized(mode))") 121 | print("request ============") 122 | Permission.request(mode) { (result) in 123 | print("isAuthorized: \(Permission.isAuthorized(mode))") 124 | print("end ================") 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Demo/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | inhibit_all_warnings! 3 | 4 | target 'Demo' do 5 | use_frameworks! 6 | 7 | # pod 'PermissionKit/Core', :path => "../" 8 | # pod 'PermissionKit/Alert', :path => "../" 9 | # pod 'PermissionKit/Location', :path => "../" 10 | pod 'PermissionKit', :path => "../" 11 | 12 | end 13 | -------------------------------------------------------------------------------- /Demo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - PermissionKit (1.6.0): 3 | - PermissionKit/Bluetooth (= 1.6.0) 4 | - PermissionKit/Camera (= 1.6.0) 5 | - PermissionKit/Contacts (= 1.6.0) 6 | - PermissionKit/Core (= 1.6.0) 7 | - PermissionKit/Event (= 1.6.0) 8 | - PermissionKit/Location (= 1.6.0) 9 | - PermissionKit/Media (= 1.6.0) 10 | - PermissionKit/Motion (= 1.6.0) 11 | - PermissionKit/Notification (= 1.6.0) 12 | - PermissionKit/Photos (= 1.6.0) 13 | - PermissionKit/Siri (= 1.6.0) 14 | - PermissionKit/Speech (= 1.6.0) 15 | - PermissionKit/Tracking (= 1.6.0) 16 | - PermissionKit/Bluetooth (1.6.0): 17 | - PermissionKit/Core 18 | - PermissionKit/Camera (1.6.0): 19 | - PermissionKit/Core 20 | - PermissionKit/Contacts (1.6.0): 21 | - PermissionKit/Core 22 | - PermissionKit/Core (1.6.0): 23 | - PermissionKit/Privacy 24 | - PermissionKit/Event (1.6.0): 25 | - PermissionKit/Core 26 | - PermissionKit/Location (1.6.0): 27 | - PermissionKit/Core 28 | - PermissionKit/Media (1.6.0): 29 | - PermissionKit/Core 30 | - PermissionKit/Motion (1.6.0): 31 | - PermissionKit/Core 32 | - PermissionKit/Notification (1.6.0): 33 | - PermissionKit/Core 34 | - PermissionKit/Photos (1.6.0): 35 | - PermissionKit/Core 36 | - PermissionKit/Privacy (1.6.0) 37 | - PermissionKit/Siri (1.6.0): 38 | - PermissionKit/Core 39 | - PermissionKit/Speech (1.6.0): 40 | - PermissionKit/Core 41 | - PermissionKit/Tracking (1.6.0): 42 | - PermissionKit/Core 43 | 44 | DEPENDENCIES: 45 | - PermissionKit (from `../`) 46 | 47 | EXTERNAL SOURCES: 48 | PermissionKit: 49 | :path: "../" 50 | 51 | SPEC CHECKSUMS: 52 | PermissionKit: afc9d0a834126aae7c03650ef519fe8793d44492 53 | 54 | PODFILE CHECKSUM: c700c41c391b70b5dcd6e1fce8cd14bae906b944 55 | 56 | COCOAPODS: 1.15.2 57 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Qutheory, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/README.md: -------------------------------------------------------------------------------- 1 |

2 | Bits 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Slack Team 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 3.1 19 | 20 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Aliases.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A single byte represented as a UInt8 3 | */ 4 | public typealias Byte = UInt8 5 | 6 | /** 7 | A byte array or collection of raw data 8 | */ 9 | public typealias Bytes = [Byte] 10 | 11 | /** 12 | A sliced collection of raw data 13 | */ 14 | public typealias BytesSlice = ArraySlice 15 | 16 | // MARK: Sizes 17 | 18 | private let _bytes = 1 19 | private let _kilobytes = _bytes * 1000 20 | private let _megabytes = _kilobytes * 1000 21 | private let _gigabytes = _megabytes * 1000 22 | 23 | extension Int { 24 | public var bytes: Int { return self } 25 | public var kilobytes: Int { return self * _kilobytes } 26 | public var megabytes: Int { return self * _megabytes } 27 | public var gigabytes: Int { return self * _gigabytes } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Base64Encoder.swift: -------------------------------------------------------------------------------- 1 | /// Encodes and decodes bytes using the 2 | /// Base64 encoding 3 | /// 4 | /// https://en.wikipedia.org/wiki/Base64 5 | public final class Base64Encoder { 6 | 7 | /// Static shared instance 8 | public static let shared = Base64Encoder.regular 9 | 10 | /// Standard Base64Encoder 11 | public static var regular: Base64Encoder { 12 | return Base64Encoder() 13 | } 14 | 15 | // Base64URLEncoder 16 | // - note: uses hyphens and underscores 17 | // in place of plus and forwardSlash 18 | public static var url: Base64Encoder { 19 | let encodeMap: Base64Encoder.ByteMap = { byte in 20 | switch byte { 21 | case 62: 22 | return .hyphen 23 | case 63: 24 | return .underscore 25 | default: 26 | return nil 27 | } 28 | } 29 | 30 | let decodeMap: Base64Encoder.ByteMap = { byte in 31 | switch byte { 32 | case Byte.hyphen: 33 | return 62 34 | case Byte.underscore: 35 | return 63 36 | default: 37 | return nil 38 | } 39 | } 40 | 41 | return Base64Encoder( 42 | padding: nil, 43 | encodeMap: encodeMap, 44 | decodeMap: decodeMap 45 | ) 46 | } 47 | 48 | /// Maps binary format to base64 encoding 49 | static let encodingTable: [Byte: Byte] = [ 50 | 0: .A, 1: .B, 2: .C, 3: .D, 51 | 4: .E, 5: .F, 6: .G, 7: .H, 52 | 8: .I, 9: .J, 10: .K, 11: .L, 53 | 12: .M, 13: .N, 14: .O, 15: .P, 54 | 16: .Q, 17: .R, 18: .S, 19: .T, 55 | 20: .U, 21: .V, 22: .W, 23: .X, 56 | 24: .Y, 25: .Z, 26: .a, 27: .b, 57 | 28: .c, 29: .d, 30: .e, 31: .f, 58 | 32: .g, 33: .h, 34: .i, 35: .j, 59 | 36: .k, 37: .l, 38: .m, 39: .n, 60 | 40: .o, 41: .p, 42: .q, 43: .r, 61 | 44: .s, 45: .t, 46: .u, 47: .v, 62 | 48: .w, 49: .x, 50: .y, 51: .z, 63 | 52: .zero, 53: .one, 54: .two, 55: .three, 64 | 56: .four, 57: .five, 58: .six, 59: .seven, 65 | 60: .eight, 61: .nine, 62: .plus, 63: .forwardSlash 66 | ] 67 | 68 | /// Maps base64 encoding into binary format 69 | static let decodingTable: [Byte: Byte] = [ 70 | .A: 0, .B: 1, .C: 2, .D: 3, 71 | .E: 4, .F: 5, .G: 6, .H: 7, 72 | .I: 8, .J: 9, .K: 10, .L: 11, 73 | .M: 12, .N: 13, .O: 14, .P: 15, 74 | .Q: 16, .R: 17, .S: 18, .T: 19, 75 | .U: 20, .V: 21, .W: 22, .X: 23, 76 | .Y: 24, .Z: 25, .a: 26, .b: 27, 77 | .c: 28, .d: 29, .e: 30, .f: 31, 78 | .g: 32, .h: 33, .i: 34, .j: 35, 79 | .k: 36, .l: 37, .m: 38, .n: 39, 80 | .o: 40, .p: 41, .q: 42, .r: 43, 81 | .s: 44, .t: 45, .u: 46, .v: 47, 82 | .w: 48, .x: 49, .y: 50, .z: 51, 83 | .zero: 52, .one: 53, .two: 54, .three: 55, 84 | .four: 56, .five: 57, .six: 58, .seven: 59, 85 | .eight: 60, .nine: 61, .plus: 62, .forwardSlash: 63 86 | ] 87 | 88 | /// Typealias for optionally mapping a byte 89 | public typealias ByteMap = (Byte) -> Byte? 90 | 91 | /// Byte to use for padding base64 92 | /// if nil, no padding will be used 93 | public let padding: Byte? 94 | 95 | /// If set, bytes returned will have priority 96 | /// over the encoding table. Encoding table 97 | /// will be used as a fallback 98 | public let encodeMap: ByteMap? 99 | 100 | /// If set, bytes returned will have priority 101 | /// over the decoding table. Decoding table 102 | /// will be used as a fallback 103 | public let decodeMap: ByteMap? 104 | 105 | /// Creates a new Base64 encoder 106 | public init( 107 | padding: Byte? = .equals, 108 | encodeMap: ByteMap? = nil, 109 | decodeMap: ByteMap? = nil 110 | ) { 111 | self.padding = padding 112 | self.encodeMap = encodeMap 113 | self.decodeMap = decodeMap 114 | } 115 | 116 | /// Encodes bytes into Base64 format 117 | public func encode(_ bytes: Bytes) -> Bytes { 118 | if bytes.count == 0 { 119 | return [] 120 | } 121 | 122 | let len = bytes.count 123 | var offset: Int = 0 124 | var c1: UInt8 125 | var c2: UInt8 126 | var result: Bytes = [] 127 | 128 | while offset < len { 129 | c1 = bytes[offset] & 0xff 130 | offset += 1 131 | result.append(encode((c1 >> 2) & 0x3f)) 132 | c1 = (c1 & 0x03) << 4 133 | if offset >= len { 134 | result.append(encode(c1 & 0x3f)) 135 | if let padding = self.padding { 136 | result.append(padding) 137 | result.append(padding) 138 | } 139 | break 140 | } 141 | 142 | c2 = bytes[offset] & 0xff 143 | offset += 1 144 | c1 |= (c2 >> 4) & 0x0f 145 | result.append(encode(c1 & 0x3f)) 146 | c1 = (c2 & 0x0f) << 2 147 | if offset >= len { 148 | result.append(encode(c1 & 0x3f)) 149 | if let padding = self.padding { 150 | result.append(padding) 151 | } 152 | break 153 | } 154 | 155 | c2 = bytes[offset] & 0xff 156 | offset += 1 157 | c1 |= (c2 >> 6) & 0x03 158 | result.append(encode(c1 & 0x3f)) 159 | result.append(encode(c2 & 0x3f)) 160 | } 161 | 162 | return result 163 | } 164 | 165 | /// Decodes bytes into binary format 166 | public func decode(_ s: Bytes) -> Bytes { 167 | let maxolen = s.count 168 | 169 | var off: Int = 0 170 | var olen: Int = 0 171 | var result = Bytes(repeating: 0, count: maxolen) 172 | 173 | var c1: Byte 174 | var c2: Byte 175 | var c3: Byte 176 | var c4: Byte 177 | var o: Byte 178 | 179 | while off < s.count - 1 && olen < maxolen { 180 | c1 = decode(s[off]) 181 | off += 1 182 | c2 = decode(s[off]) 183 | off += 1 184 | if c1 == Byte.max || c2 == Byte.max { 185 | break 186 | } 187 | 188 | o = c1 << 2 189 | o |= (c2 & 0x30) >> 4 190 | result[olen] = o 191 | olen += 1 192 | if olen >= maxolen || off >= s.count { 193 | break 194 | } 195 | 196 | c3 = decode(s[off]) 197 | off += 1 198 | if c3 == Byte.max { 199 | break 200 | } 201 | 202 | o = (c2 & 0x0f) << 4 203 | o |= (c3 & 0x3c) >> 2 204 | result[olen] = o 205 | olen += 1 206 | if olen >= maxolen || off >= s.count { 207 | break 208 | } 209 | 210 | c4 = decode(s[off]) 211 | off += 1 212 | if c4 == Byte.max { 213 | break 214 | } 215 | o = (c3 & 0x03) << 6 216 | o |= c4 217 | result[olen] = o 218 | olen += 1 219 | } 220 | 221 | return Array(result[0.. Byte { 227 | return encodeMap?(x) 228 | ?? Base64Encoder.encodingTable[x] 229 | ?? Byte.max 230 | } 231 | 232 | private func decode(_ x: Byte) -> Byte { 233 | return decodeMap?(x) 234 | ?? Base64Encoder.decodingTable[x] 235 | ?? Byte.max 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Byte+Alphabet.swift: -------------------------------------------------------------------------------- 1 | extension Byte { 2 | /// A 3 | public static let A: Byte = 0x41 4 | 5 | /// B 6 | public static let B: Byte = 0x42 7 | 8 | /// C 9 | public static let C: Byte = 0x43 10 | 11 | /// D 12 | public static let D: Byte = 0x44 13 | 14 | /// E 15 | public static let E: Byte = 0x45 16 | 17 | /// F 18 | public static let F: Byte = 0x46 19 | 20 | /// F 21 | public static let G: Byte = 0x47 22 | 23 | /// F 24 | public static let H: Byte = 0x48 25 | 26 | /// F 27 | public static let I: Byte = 0x49 28 | 29 | /// F 30 | public static let J: Byte = 0x4A 31 | 32 | /// F 33 | public static let K: Byte = 0x4B 34 | 35 | /// F 36 | public static let L: Byte = 0x4C 37 | 38 | /// F 39 | public static let M: Byte = 0x4D 40 | 41 | /// F 42 | public static let N: Byte = 0x4E 43 | 44 | /// F 45 | public static let O: Byte = 0x4F 46 | 47 | /// F 48 | public static let P: Byte = 0x50 49 | 50 | /// F 51 | public static let Q: Byte = 0x51 52 | 53 | /// F 54 | public static let R: Byte = 0x52 55 | 56 | /// F 57 | public static let S: Byte = 0x53 58 | 59 | /// F 60 | public static let T: Byte = 0x54 61 | 62 | /// F 63 | public static let U: Byte = 0x55 64 | 65 | /// F 66 | public static let V: Byte = 0x56 67 | 68 | /// F 69 | public static let W: Byte = 0x57 70 | 71 | /// F 72 | public static let X: Byte = 0x58 73 | 74 | /// F 75 | public static let Y: Byte = 0x59 76 | 77 | /// Z 78 | public static let Z: Byte = 0x5A 79 | } 80 | 81 | extension Byte { 82 | /// a 83 | public static let a: Byte = 0x61 84 | 85 | /// b 86 | public static let b: Byte = 0x62 87 | 88 | /// c 89 | public static let c: Byte = 0x63 90 | 91 | /// d 92 | public static let d: Byte = 0x64 93 | 94 | /// e 95 | public static let e: Byte = 0x65 96 | 97 | /// f 98 | public static let f: Byte = 0x66 99 | 100 | /// g 101 | public static let g: Byte = 0x67 102 | 103 | /// h 104 | public static let h: Byte = 0x68 105 | 106 | /// i 107 | public static let i: Byte = 0x69 108 | 109 | /// j 110 | public static let j: Byte = 0x6A 111 | 112 | /// k 113 | public static let k: Byte = 0x6B 114 | 115 | /// l 116 | public static let l: Byte = 0x6C 117 | 118 | /// m 119 | public static let m: Byte = 0x6D 120 | 121 | /// n 122 | public static let n: Byte = 0x6E 123 | 124 | /// o 125 | public static let o: Byte = 0x6F 126 | 127 | /// p 128 | public static let p: Byte = 0x70 129 | 130 | /// q 131 | public static let q: Byte = 0x71 132 | 133 | /// r 134 | public static let r: Byte = 0x72 135 | 136 | /// s 137 | public static let s: Byte = 0x73 138 | 139 | /// t 140 | public static let t: Byte = 0x74 141 | 142 | /// u 143 | public static let u: Byte = 0x75 144 | 145 | /// v 146 | public static let v: Byte = 0x76 147 | 148 | /// w 149 | public static let w: Byte = 0x77 150 | 151 | /// x 152 | public static let x: Byte = 0x78 153 | 154 | /// y 155 | public static let y: Byte = 0x79 156 | 157 | /// z 158 | public static let z: Byte = 0x7A 159 | } 160 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Byte+ControlCharacters.swift: -------------------------------------------------------------------------------- 1 | extension Byte { 2 | /// '\t' 3 | public static let horizontalTab: Byte = 0x9 4 | 5 | /// '\n' 6 | public static let newLine: Byte = 0xA 7 | 8 | /// '\r' 9 | public static let carriageReturn: Byte = 0xD 10 | 11 | /// ' ' 12 | public static let space: Byte = 0x20 13 | 14 | /// ! 15 | public static let exclamation: Byte = 0x21 16 | 17 | /// " 18 | public static let quote: Byte = 0x22 19 | 20 | /// # 21 | public static let numberSign: Byte = 0x23 22 | 23 | /// $ 24 | public static let dollar: Byte = 0x24 25 | 26 | /// % 27 | public static let percent: Byte = 0x25 28 | 29 | /// & 30 | public static let ampersand: Byte = 0x26 31 | 32 | /// ' 33 | public static let apostrophe: Byte = 0x27 34 | 35 | /// ( 36 | public static let leftParenthesis: Byte = 0x28 37 | 38 | /// ) 39 | public static let rightParenthesis: Byte = 0x29 40 | 41 | /// * 42 | public static let asterisk: Byte = 0x2A 43 | 44 | /// + 45 | public static let plus: Byte = 0x2B 46 | 47 | /// , 48 | public static let comma: Byte = 0x2C 49 | 50 | /// - 51 | public static let hyphen: Byte = 0x2D 52 | 53 | /// . 54 | public static let period: Byte = 0x2E 55 | 56 | /// / 57 | public static let forwardSlash: Byte = 0x2F 58 | 59 | /// \ 60 | public static let backSlash: Byte = 0x5C 61 | 62 | /// : 63 | public static let colon: Byte = 0x3A 64 | 65 | /// ; 66 | public static let semicolon: Byte = 0x3B 67 | 68 | /// = 69 | public static let equals: Byte = 0x3D 70 | 71 | /// ? 72 | public static let questionMark: Byte = 0x3F 73 | 74 | /// @ 75 | public static let at: Byte = 0x40 76 | 77 | /// [ 78 | public static let leftSquareBracket: Byte = 0x5B 79 | 80 | /// ] 81 | public static let rightSquareBracket: Byte = 0x5D 82 | 83 | /// _ 84 | public static let underscore: Byte = 0x5F 85 | 86 | /// ~ 87 | public static let tilda: Byte = 0x7E 88 | 89 | /// { 90 | public static let leftCurlyBracket: Byte = 0x7B 91 | 92 | /// } 93 | public static let rightCurlyBracket: Byte = 0x7D 94 | } 95 | 96 | extension Byte { 97 | /** 98 | Defines the `crlf` used to denote 99 | line breaks in HTTP and many other 100 | formatters 101 | */ 102 | public static let crlf: Bytes = [ 103 | .carriageReturn, 104 | .newLine 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Byte+Convenience.swift: -------------------------------------------------------------------------------- 1 | extension Byte { 2 | /** 3 | Returns whether or not the given byte can be considered UTF8 whitespace 4 | */ 5 | public var isWhitespace: Bool { 6 | return self == .space || self == .newLine || self == .carriageReturn || self == .horizontalTab 7 | } 8 | 9 | /** 10 | Returns whether or not the given byte is an arabic letter 11 | */ 12 | public var isLetter: Bool { 13 | return (.a ... .z).contains(self) || (.A ... .Z).contains(self) 14 | } 15 | 16 | /** 17 | Returns whether or not a given byte represents a UTF8 digit 0 through 9 18 | */ 19 | public var isDigit: Bool { 20 | return (.zero ... .nine).contains(self) 21 | } 22 | 23 | /** 24 | Returns whether or not a given byte represents a UTF8 digit 0 through 9, or an arabic letter 25 | */ 26 | public var isAlphanumeric: Bool { 27 | return isLetter || isDigit 28 | } 29 | 30 | /** 31 | Returns whether a given byte can be interpreted as a hex value in UTF8, ie: 0-9, a-f, A-F. 32 | */ 33 | public var isHexDigit: Bool { 34 | return (.zero ... .nine).contains(self) || (.A ... .F).contains(self) || (.a ... .f).contains(self) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Byte+PatternMatching.swift: -------------------------------------------------------------------------------- 1 | // MARK: Byte 2 | 3 | public func ~=(pattern: Byte, value: Byte) -> Bool { 4 | return pattern == value 5 | } 6 | 7 | public func ~=(pattern: Byte, value: BytesSlice) -> Bool { 8 | return value.contains(pattern) 9 | } 10 | 11 | public func ~=(pattern: Byte, value: Bytes) -> Bool { 12 | return value.contains(pattern) 13 | } 14 | 15 | // MARK: Bytes 16 | 17 | public func ~=(pattern: Bytes, value: Byte) -> Bool { 18 | return pattern.contains(value) 19 | } 20 | 21 | public func ~=(pattern: Bytes, value: Bytes) -> Bool { 22 | return pattern == value 23 | } 24 | 25 | public func ~=(pattern: Bytes, value: BytesSlice) -> Bool { 26 | return pattern == Bytes(value) 27 | } 28 | 29 | // MARK: BytesSlice 30 | 31 | 32 | public func ~=(pattern: BytesSlice, value: Byte) -> Bool { 33 | return pattern.contains(value) 34 | } 35 | 36 | public func ~=(pattern: BytesSlice, value: BytesSlice) -> Bool { 37 | return pattern == value 38 | } 39 | 40 | public func ~=(pattern: BytesSlice, value: Bytes) -> Bool { 41 | return Bytes(pattern) == value 42 | } 43 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Byte+Random.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | @_exported import Glibc 3 | #else 4 | @_exported import Darwin.C 5 | #endif 6 | 7 | extension Byte { 8 | private static let max32 = UInt32(Byte.max) 9 | 10 | /** 11 | Create a single random byte 12 | */ 13 | public static func randomByte() -> Byte { 14 | #if os(Linux) 15 | let val = Byte(Glibc.random() % Int(max32)) 16 | #else 17 | let val = Byte(arc4random_uniform(max32)) 18 | #endif 19 | return val 20 | } 21 | } 22 | 23 | extension UnsignedInteger { 24 | /** 25 | Return a random value for the given type. 26 | This should NOT be considered cryptographically secure. 27 | */ 28 | public static func random() -> Self { 29 | let size = MemoryLayout.size 30 | var bytes: [Byte] = [] 31 | (1...size).forEach { _ in 32 | let randomByte = Byte.randomByte() 33 | bytes.append(randomByte) 34 | } 35 | return Self(bytes: bytes) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Byte+UTF8Numbers.swift: -------------------------------------------------------------------------------- 1 | extension Byte { 2 | 3 | /// 0 in utf8 4 | public static let zero: Byte = 0x30 5 | 6 | /// 1 in utf8 7 | public static let one: Byte = 0x31 8 | 9 | /// 2 in utf8 10 | public static let two: Byte = 0x32 11 | 12 | /// 3 in utf8 13 | public static let three: Byte = 0x33 14 | 15 | /// 4 in utf8 16 | public static let four: Byte = 0x34 17 | 18 | /// 5 in utf8 19 | public static let five: Byte = 0x35 20 | 21 | /// 6 in utf8 22 | public static let six: Byte = 0x36 23 | 24 | /// 7 in utf8 25 | public static let seven: Byte = 0x37 26 | 27 | /// 8 in utf8 28 | public static let eight: Byte = 0x38 29 | 30 | /// 9 in utf8 31 | public static let nine: Byte = 0x39 32 | } 33 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/ByteSequence+Conversions.swift: -------------------------------------------------------------------------------- 1 | extension Sequence where Iterator.Element == Byte { 2 | /// Converts a slice of bytes to 3 | /// string. Courtesy of @vzsg 4 | public func makeString() -> String { 5 | let array = Array(self) + [0] 6 | 7 | return array.withUnsafeBytes { rawBuffer in 8 | guard let pointer = rawBuffer.baseAddress?.assumingMemoryBound(to: CChar.self) else { return nil } 9 | return String(validatingUTF8: pointer) 10 | } ?? "" 11 | } 12 | 13 | /** 14 | Converts a byte representation 15 | of a hex value into an `Int`. 16 | as opposed to it's Decimal value 17 | 18 | ie: "10" == 16, not 10 19 | */ 20 | public var hexInt: Int? { 21 | var int: Int = 0 22 | 23 | for byte in self { 24 | int = int * 16 25 | 26 | if byte >= .zero && byte <= .nine { 27 | int += Int(byte - .zero) 28 | } else if byte >= .A && byte <= .F { 29 | int += Int(byte - .A) + 10 30 | } else if byte >= .a && byte <= .f { 31 | int += Int(byte - .a) + 10 32 | } else { 33 | return nil 34 | } 35 | } 36 | 37 | return int 38 | } 39 | 40 | /** 41 | Converts a utf8 byte representation 42 | of a decimal value into an `Int` 43 | as opposed to it's Hex value, 44 | 45 | ie: "10" == 10, not 16 46 | */ 47 | public var decimalInt: Int? { 48 | var int: Int = 0 49 | 50 | for byte in self { 51 | int = int * 10 52 | if byte.isDigit { 53 | int += Int(byte - .zero) 54 | } else { 55 | return nil 56 | } 57 | } 58 | 59 | return int 60 | } 61 | 62 | /** 63 | Transforms anything between Byte.A ... Byte.Z 64 | into the range Byte.a ... Byte.z 65 | */ 66 | public var lowercased: Bytes { 67 | var data = Bytes() 68 | 69 | for byte in self { 70 | if (.A ... .Z).contains(byte) { 71 | data.append(byte + (.a - .A)) 72 | } else { 73 | data.append(byte) 74 | } 75 | } 76 | 77 | return data 78 | } 79 | 80 | /** 81 | Transforms anything between Byte.a ... Byte.z 82 | into the range Byte.A ... Byte.Z 83 | */ 84 | public var uppercased: Bytes { 85 | var bytes = Bytes() 86 | 87 | for byte in self { 88 | if (.a ... .z).contains(byte) { 89 | bytes.append(byte - (.a - .A)) 90 | } else { 91 | bytes.append(byte) 92 | } 93 | } 94 | 95 | return bytes 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Bytes+Base64.swift: -------------------------------------------------------------------------------- 1 | extension Sequence where Iterator.Element == Byte { 2 | public var base64Encoded: Bytes { 3 | let bytes = Array(self) 4 | return Base64Encoder.shared.encode(bytes) 5 | } 6 | 7 | public var base64Decoded: Bytes { 8 | let bytes = Array(self) 9 | return Base64Encoder.shared.decode(bytes) 10 | } 11 | } 12 | 13 | extension Sequence where Iterator.Element == Byte { 14 | public var base64URLEncoded: Bytes { 15 | let bytes = Array(self) 16 | return Base64Encoder.url.encode(bytes) 17 | } 18 | 19 | public var base64URLDecoded: Bytes { 20 | let bytes = Array(self) 21 | return Base64Encoder.url.decode(bytes) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Bytes+Hex.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Sequence where Iterator.Element == Byte { 4 | public var hexEncoded: Bytes { 5 | let bytes = Array(self) 6 | return HexEncoder.shared.encode(bytes) 7 | } 8 | 9 | public var hexDecoded: Bytes { 10 | let bytes = Array(self) 11 | return HexEncoder.shared.decode(bytes) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Bytes+Percent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Sequence where Iterator.Element == Byte { 4 | public var percentDecoded: Bytes { 5 | return makeString() 6 | .removingPercentEncoding? 7 | .makeBytes() ?? [] 8 | } 9 | 10 | public var percentEncodedForURLQuery: Bytes { 11 | return makeString() 12 | .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)? 13 | .makeBytes() ?? [] 14 | } 15 | 16 | public var percentEncodedForURLPath: Bytes { 17 | return makeString() 18 | .addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)? 19 | .makeBytes() ?? [] 20 | } 21 | 22 | public var percentEncodedForURLHost: Bytes { 23 | return makeString() 24 | .addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)? 25 | .makeBytes() ?? [] 26 | } 27 | 28 | public var percentEncodedForURLFragment: Bytes { 29 | return makeString() 30 | .addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)? 31 | .makeBytes() ?? [] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/BytesConvertible.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Used for objects that can be represented as Bytes 3 | */ 4 | public protocol BytesRepresentable { 5 | func makeBytes() throws -> Bytes 6 | } 7 | 8 | /** 9 | Used for objects that can be initialized with Bytes 10 | */ 11 | public protocol BytesInitializable { 12 | init(bytes: Bytes) throws 13 | } 14 | 15 | /** 16 | Used for objects that can be initialized with, and represented by, Bytes 17 | */ 18 | public protocol BytesConvertible: BytesRepresentable, BytesInitializable { } 19 | 20 | extension BytesInitializable { 21 | public init(bytes: BytesRepresentable) throws { 22 | let bytes = try bytes.makeBytes() 23 | try self.init(bytes: bytes) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Data+BytesConvertible.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Data: BytesConvertible { 4 | public func makeBytes() -> Bytes { 5 | var array = Bytes(repeating: 0, count: count) 6 | let buffer = UnsafeMutableBufferPointer(start: &array, count: count) 7 | _ = copyBytes(to: buffer) 8 | return array 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/HexEncoder.swift: -------------------------------------------------------------------------------- 1 | /// Encodes and decodes bytes using the 2 | /// Hexadeicmal encoding 3 | /// 4 | /// https://en.wikipedia.org/wiki/Hexadecimal 5 | public final class HexEncoder { 6 | /// Maps binary format to hex encoding 7 | static let encodingTable: [Byte: Byte] = [ 8 | 0: .zero, 1: .one, 2: .two, 3: .three, 9 | 4: .four, 5: .five, 6: .six, 7: .seven, 10 | 8: .eight, 9: .nine, 10: .a, 11: .b, 11 | 12: .c, 13: .d, 14: .e, 15: .f 12 | ] 13 | 14 | /// Maps hex encoding to binary format 15 | /// - note: Supports upper and lowercase 16 | static let decodingTable: [Byte: Byte] = [ 17 | .zero: 0, .one: 1, .two: 2, .three: 3, 18 | .four: 4, .five: 5, .six: 6, .seven: 7, 19 | .eight: 8, .nine: 9, .a: 10, .b: 11, 20 | .c: 12, .d: 13, .e: 14, .f: 15, 21 | .A: 10, .B: 11, .C: 12, .D: 13, 22 | .E: 14, .F: 15 23 | ] 24 | 25 | /// Static shared instance 26 | public static let shared = HexEncoder() 27 | 28 | /// When true, the encoder will discard 29 | /// any unknown characters while decoding. 30 | /// When false, undecodable characters will 31 | /// cause an early return. 32 | public let ignoreUndecodableCharacters: Bool 33 | 34 | /// Creates a new Hexadecimal encoder 35 | public init(ignoreUndecodableCharacters: Bool = true) { 36 | self.ignoreUndecodableCharacters = ignoreUndecodableCharacters 37 | } 38 | 39 | /// Encodes bytes into Hexademical format 40 | public func encode(_ message: Bytes) -> Bytes { 41 | var encoded: Bytes = [] 42 | 43 | for byte in message { 44 | // move the top half of the byte down 45 | // 0x12345678 becomes 0x00001234 46 | let upper = byte >> 4 47 | 48 | // zero out the top half of the byte 49 | // 0x12345678 becomes 0x00005678 50 | let lower = byte & 0xF 51 | 52 | // encode the 4-bit numbers 53 | // using the 0-f encoding (2^4=16) 54 | encoded.append(encode(upper)) 55 | encoded.append(encode(lower)) 56 | } 57 | 58 | return encoded 59 | } 60 | 61 | /// Decodes hexadecimally encoded bytes into 62 | /// binary format 63 | public func decode(_ message: Bytes) -> Bytes { 64 | var decoded: Bytes = [] 65 | 66 | // create an iterator to easily 67 | // fetch two at a time 68 | var i = message.makeIterator() 69 | 70 | // take bytes two at a time 71 | while let c1 = i.next(), let c2 = i.next() { 72 | // decode the first character from 73 | // letter representation to 4-bit number 74 | // e.g, "1" becomes 0x00000001 75 | let upper = decode(c1) 76 | guard upper != Byte.max || ignoreUndecodableCharacters else { 77 | return decoded 78 | } 79 | 80 | // decode the second character from 81 | // letter representation to a 4-bit number 82 | let lower = decode(c2) 83 | guard lower != Byte.max || ignoreUndecodableCharacters else { 84 | return decoded 85 | } 86 | 87 | // combine the two 4-bit numbers back 88 | // into the original byte, shifting 89 | // the first back up to its 8-bit position 90 | // 91 | // 0x00001234 << 4 | 0x00005678 92 | // becomes: 93 | // 0x12345678 94 | let byte = upper << 4 | lower 95 | 96 | decoded.append(byte) 97 | } 98 | 99 | return decoded 100 | } 101 | 102 | // MARK: Private 103 | 104 | private func encode(_ byte: Byte) -> Byte { 105 | return HexEncoder.encodingTable[byte] ?? Byte.max 106 | } 107 | 108 | private func decode(_ byte: Byte) -> Byte { 109 | return HexEncoder.decodingTable[byte] ?? Byte.max 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/Operators.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Append the right-hand byte to the end of the bytes array 3 | */ 4 | public func +=(lhs: inout Bytes, rhs: Byte) { 5 | lhs.append(rhs) 6 | } 7 | 8 | /** 9 | Append the contents of the byteslice to the end of the bytes array 10 | */ 11 | public func +=(lhs: inout Bytes, rhs: BytesSlice) { 12 | lhs += Array(rhs) 13 | } 14 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/String+BytesConvertible.swift: -------------------------------------------------------------------------------- 1 | extension String: BytesConvertible { 2 | /** 3 | UTF8 Array representation of string 4 | */ 5 | public func makeBytes() -> Bytes { 6 | return Bytes(utf8) 7 | } 8 | 9 | /** 10 | Initializes a string with a UTF8 byte array 11 | */ 12 | public init(bytes: Bytes) { 13 | self = bytes.makeString() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/UnsignedInteger+BytesConvertible.swift: -------------------------------------------------------------------------------- 1 | extension UInt8: BytesConvertible {} 2 | extension UInt16: BytesConvertible {} 3 | extension UInt32: BytesConvertible {} 4 | extension UInt64: BytesConvertible {} 5 | 6 | extension UnsignedInteger { 7 | /** 8 | Bytes are concatenated to make an Unsigned Integer Object. 9 | 10 | [0b1111_1011, 0b0000_1111] 11 | => 12 | 0b1111_1011_0000_1111 13 | */ 14 | public init(bytes: Bytes) { 15 | // 8 bytes in UInt64, etc. clips overflow 16 | let prefix = bytes.suffix(MemoryLayout.size) 17 | var value: UIntMax = 0 18 | prefix.forEach { byte in 19 | value <<= 8 // 1 byte is 8 bits 20 | value |= byte.toUIntMax() 21 | } 22 | 23 | self.init(value) 24 | } 25 | 26 | /** 27 | Convert an Unsigned integer into its collection of bytes 28 | 29 | 0b1111_1011_0000_1111 30 | => 31 | [0b1111_1011, 0b0000_1111] 32 | ... etc. 33 | */ 34 | public func makeBytes() -> Bytes { 35 | let byteMask: Self = 0b1111_1111 36 | let size = MemoryLayout.size 37 | var copy = self 38 | var bytes: [Byte] = [] 39 | (1...size).forEach { _ in 40 | let next = copy & byteMask 41 | let byte = Byte(next.toUIntMax()) 42 | bytes.insert(byte, at: 0) 43 | copy.shiftRight(8) 44 | } 45 | return bytes 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Demo/Pods/Bits/Sources/Bits/UnsignedInteger+Shifting.swift: -------------------------------------------------------------------------------- 1 | extension UnsignedInteger { 2 | /** 3 | Returns whether or not a given bitMask is part of the caller 4 | */ 5 | public func containsMask(_ mask: Self) -> Bool { 6 | return (self & mask) == mask 7 | } 8 | } 9 | 10 | extension UnsignedInteger { 11 | /** 12 | A right bit shifter that is supported without the need for a concrete type. 13 | */ 14 | mutating func shiftRight(_ places: Int) { 15 | (1...places).forEach { _ in 16 | self /= 2 17 | } 18 | } 19 | 20 | /** 21 | A bit shifter that is supported without the need for a concrete type. 22 | */ 23 | mutating func shiftLeft(_ places: Int) { 24 | (1...places).forEach { _ in 25 | self *= 2 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Demo/Pods/Core/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Qutheory, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Demo/Pods/Core/README.md: -------------------------------------------------------------------------------- 1 |

2 | Core 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Slack Team 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 3.1 19 | 20 |

21 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Array.swift: -------------------------------------------------------------------------------- 1 | extension Array { 2 | /** 3 | Turn into an array of various chunk sizes 4 | 5 | Last component may not be equal size as others. 6 | 7 | [1,2,3,4,5].chunked(size: 2) 8 | == 9 | [[1,2],[3,4],[5]] 10 | */ 11 | public func chunked(size: Int) -> [[Element]] { 12 | return stride(from: 0, to: count, by: size).map { startIndex in 13 | let next = startIndex.advanced(by: size) 14 | let end = next <= endIndex ? next : endIndex 15 | return Array(self[startIndex ..< end]) 16 | } 17 | } 18 | } 19 | 20 | extension Array where Element: Hashable { 21 | /** 22 | Trims the head and tail of the array to remove contained elements. 23 | 24 | [0,1,2,1,0,1,0,0,0,0].trimmed([0]) 25 | // == [1,2,1,0,1] 26 | 27 | This function is intended to be as performant as possible, which is part of the reason 28 | why some of the underlying logic may seem a bit more tedious than is necessary 29 | */ 30 | public func trimmed(_ elements: [Element]) -> SubSequence { 31 | guard !isEmpty else { return [] } 32 | 33 | let lastIdx = self.count - 1 34 | var leadingIterator = self.indices.makeIterator() 35 | var trailingIterator = leadingIterator 36 | 37 | var leading = 0 38 | var trailing = lastIdx 39 | while let next = leadingIterator.next(), elements.contains(self[next]) { 40 | leading += 1 41 | } 42 | while let next = trailingIterator.next(), elements.contains(self[lastIdx - next]) { 43 | trailing -= 1 44 | } 45 | 46 | guard trailing >= leading else { return [] } 47 | return self[leading...trailing] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Bits.swift: -------------------------------------------------------------------------------- 1 | @_exported import Bits 2 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Cache.swift: -------------------------------------------------------------------------------- 1 | public typealias Size = Int 2 | 3 | public protocol Cacheable { 4 | func cacheSize() -> Size 5 | } 6 | 7 | public final class SystemCache { 8 | public let maxSize: Size 9 | 10 | private var ordered: OrderedDictionary = .init() 11 | 12 | public init(maxSize: Size) { 13 | self.maxSize = maxSize 14 | } 15 | 16 | public subscript(key: String) -> Wrapped? { 17 | get { 18 | return ordered[key] 19 | } 20 | set { 21 | ordered[key] = newValue 22 | vent() 23 | } 24 | } 25 | 26 | private func vent() { 27 | var dropTotal = totalSize() - maxSize 28 | while dropTotal > 0 { 29 | let next = dropOldest() 30 | guard let size = next?.cacheSize() else { break } 31 | dropTotal -= size 32 | } 33 | } 34 | 35 | private func totalSize() -> Size { 36 | return ordered.unorderedItems.map { $0.cacheSize() } .reduce(0, +) 37 | } 38 | 39 | private func dropOldest() -> Wrapped? { 40 | guard let oldest = ordered.oldest else { return nil } 41 | ordered[oldest.key] = nil 42 | return oldest.value 43 | } 44 | } 45 | 46 | fileprivate struct OrderedDictionary { 47 | fileprivate var oldest: (key: Key, value: Value)? { 48 | guard let key = list.first, let value = backing[key] else { return nil } 49 | return (key, value) 50 | } 51 | 52 | fileprivate var newest: (key: Key, value: Value)? { 53 | guard let key = list.last, let value = backing[key] else { return nil } 54 | return (key, value) 55 | } 56 | 57 | fileprivate var items: [Value] { 58 | return list.flatMap { backing[$0] } 59 | } 60 | 61 | // theoretically slightly faster 62 | fileprivate var unorderedItems: LazyMapCollection, Value> { 63 | return backing.values 64 | } 65 | 66 | private var list: [Key] = [] 67 | private var backing: [Key: Value] = [:] 68 | 69 | fileprivate subscript(key: Key) -> Value? { 70 | mutating get { 71 | if let existing = backing[key] { 72 | return existing 73 | } else { 74 | remove(key) 75 | return nil 76 | } 77 | } 78 | set { 79 | if let newValue = newValue { 80 | // overwrite anything that might exist 81 | remove(key) 82 | backing[key] = newValue 83 | list.append(key) 84 | 85 | } else { 86 | backing[key] = nil 87 | remove(key) 88 | } 89 | } 90 | } 91 | 92 | fileprivate subscript(idx: Int) -> (key: Key, value: Value)? { 93 | guard idx < list.count, idx >= 0 else { return nil } 94 | let key = list[idx] 95 | guard let value = backing[key] else { return nil } 96 | return (key, value) 97 | } 98 | 99 | fileprivate mutating func remove(_ key: Key) { 100 | if let idx = list.index(of: key) { 101 | list.remove(at: idx) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Collection+Safe.swift: -------------------------------------------------------------------------------- 1 | extension Collection { 2 | /** 3 | Safely access the contents of a collection. Nil if outside of bounds. 4 | */ 5 | public subscript(safe idx: Index) -> Iterator.Element? { 6 | guard startIndex <= idx else { return nil } 7 | // NOT >=, endIndex is "past the end" 8 | guard endIndex > idx else { return nil } 9 | return self[idx] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/DataFile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Basic Foundation implementation of FileProtocols 4 | public final class DataFile: FileProtocol { 5 | /// Working directory will be used when relative 6 | /// paths are supplied 7 | public let workDir: String 8 | 9 | /// Creates a DataFile instance with optional workdir. 10 | public init(workDir: String) { 11 | self.workDir = workDir 12 | } 13 | 14 | /// @see - FileProtocol.load 15 | public func read(at path: String) throws -> Bytes { 16 | let path = makeAbsolute(path: path) 17 | guard let data = NSData(contentsOfFile: path) else { 18 | throw DataFileError.load(path: path) 19 | } 20 | 21 | var bytes = Bytes(repeating: 0, count: data.length) 22 | data.getBytes(&bytes, length: bytes.count) 23 | return bytes 24 | } 25 | 26 | /// @see - FileProtocol.save 27 | public func write(_ bytes: Bytes, to path: String) throws { 28 | let path = makeAbsolute(path: path) 29 | if !fileExists(at: path) { 30 | try create(at: path, bytes: bytes) 31 | } else { 32 | try write(to: path, bytes: bytes) 33 | } 34 | } 35 | 36 | /// @see - FileProtocol.delete 37 | public func delete(at path: String) throws { 38 | let path = makeAbsolute(path: path) 39 | try FileManager.default.removeItem(atPath: path) 40 | } 41 | 42 | // MARK: Private 43 | 44 | private func makeAbsolute(path: String) -> String { 45 | return path.hasPrefix("/") ? path : workDir + path 46 | } 47 | 48 | private func create(at path: String, bytes: Bytes) throws { 49 | let data = Data(bytes: bytes) 50 | let success = FileManager.default.createFile( 51 | atPath: path, 52 | contents: data, 53 | attributes: nil 54 | ) 55 | guard success else { throw DataFileError.create(path: path) } 56 | } 57 | 58 | private func fileExists(at path: String) -> Bool { 59 | return FileManager.default.fileExists(atPath: path) 60 | } 61 | 62 | private func write(to path: String, bytes: Bytes) throws { 63 | let bytes = Data(bytes: bytes) 64 | 65 | let url = URL(fileURLWithPath: path) 66 | try bytes.write(to: url) 67 | } 68 | } 69 | 70 | extension DataFile: EmptyInitializable { 71 | public convenience init() { 72 | self.init(workDir: workingDirectory()) 73 | } 74 | } 75 | 76 | // MARK: Error 77 | 78 | public enum DataFileError: Error { 79 | case create(path: String) 80 | case load(path: String) 81 | case unspecified(Swift.Error) 82 | } 83 | 84 | extension DataFileError: Debuggable { 85 | public var identifier: String { 86 | switch self { 87 | case .create: 88 | return "create" 89 | case .load: 90 | return "load" 91 | case .unspecified: 92 | return "unspecified" 93 | } 94 | } 95 | 96 | public var reason: String { 97 | switch self { 98 | case .create(let path): 99 | return "unable to create the file at path \(path)" 100 | case .load(let path): 101 | return "unable to load file at path \(path)" 102 | case .unspecified(let error): 103 | return "received an unspecified or extended error: \(error)" 104 | } 105 | } 106 | 107 | public var possibleCauses: [String] { 108 | switch self { 109 | case .create: 110 | return [ 111 | "missing write permissions at specified path", 112 | "attempted to write corrupted data", 113 | "system issue" 114 | ] 115 | case .load: 116 | return [ 117 | "file doesn't exist", 118 | "missing read permissions at specified path", 119 | "data read is corrupted", 120 | "system issue" 121 | ] 122 | case .unspecified: 123 | return [ 124 | "received an error not originally supported by this version" 125 | ] 126 | } 127 | } 128 | 129 | public var suggestedFixes: [String] { 130 | return [ 131 | "ensure that file permissions are correct for specified paths" 132 | ] 133 | } 134 | 135 | public var documentationLinks: [String] { 136 | return [ 137 | "https://developer.apple.com/reference/foundation/filemanager", 138 | ] 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Dispatch.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | /** 4 | A simple background function that uses dispatch to send to a global queue 5 | */ 6 | public func background(function: @escaping () -> Void) { 7 | DispatchQueue.global().async(execute: function) 8 | } 9 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/DispatchTime+Utilities.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Dispatch 3 | 4 | extension Double { 5 | internal var nanoseconds: UInt64 { 6 | return UInt64(self * Double(1_000_000_000)) 7 | } 8 | } 9 | 10 | extension DispatchTime { 11 | /** 12 | Create a dispatch time for a given seconds from now. 13 | */ 14 | public init(secondsFromNow: Double) { 15 | let uptime = DispatchTime.now().rawValue + secondsFromNow.nanoseconds 16 | self.init(uptimeNanoseconds: uptime) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/EmptyInitializable.swift: -------------------------------------------------------------------------------- 1 | /// Types conforming to this protocol can 2 | /// be initialized with no arguments, allowing 3 | /// protocols to add static convenience methods. 4 | public protocol EmptyInitializable { 5 | init() throws 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import Debugging 2 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Extendable.swift: -------------------------------------------------------------------------------- 1 | /// Types conforming to this protocol can store 2 | /// arbitrary key-value data. 3 | /// 4 | /// Extensions can utilize this arbitrary data store 5 | /// to simulate optional stored properties. 6 | public protocol Extendable { 7 | /// Arbitrary key-value data store. 8 | var extend: [String: Any] { get set } 9 | } 10 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/FileProtocol.swift: -------------------------------------------------------------------------------- 1 | /// Objects conforming to this protocol 2 | /// can load and save files to a persistent 3 | /// data store. 4 | public protocol FileProtocol { 5 | /// Load the bytes at a given path 6 | func read(at path: String) throws -> Bytes 7 | 8 | /// Save the bytes to a given path 9 | func write(_ bytes: Bytes, to path: String) throws 10 | 11 | /// Deletes the file at a given path 12 | func delete(at path: String) throws 13 | } 14 | 15 | extension FileProtocol where Self: EmptyInitializable { 16 | /// Load the bytes at a given path 17 | public static func read(at path: String) throws -> Bytes { 18 | return try Self().read(at: path) 19 | } 20 | 21 | /// Save the bytes to a given path 22 | public static func write(_ bytes: Bytes, to path: String) throws { 23 | try Self().write(bytes, to: path) 24 | } 25 | 26 | /// Deletes the file at a given path 27 | public static func delete(at path: String) throws { 28 | try Self().delete(at: path) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Int+Hex.swift: -------------------------------------------------------------------------------- 1 | extension SignedInteger { 2 | /** 3 | Convert a Signed integer into a hex string representation 4 | 5 | 255 6 | => 7 | FF 8 | 9 | NOTE: Will always return UPPERCASED VALUES 10 | */ 11 | public var hex: String { 12 | return String(self, radix: 16).uppercased() 13 | } 14 | } 15 | 16 | extension UnsignedInteger { 17 | /** 18 | Convert a Signed integer into a hex string representation 19 | 20 | 255 21 | => 22 | FF 23 | 24 | NOTE: Will always return UPPERCASED VALUES 25 | */ 26 | public var hex: String { 27 | return String(self, radix: 16).uppercased() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Lock.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSLock { 4 | public func locked(closure: () throws -> Void) rethrows { 5 | lock() 6 | defer { unlock() } // MUST be deferred to ensure lock releases if throws 7 | try closure() 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Portal.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Dispatch 3 | 4 | /** 5 | There was an error thrown by the portal itself vs a user thrown variable 6 | */ 7 | public enum PortalError: String, Debuggable { 8 | /** 9 | Portal was destroyed w/o being closed 10 | */ 11 | case notClosed 12 | 13 | /** 14 | Portal timedOut before it was closed. 15 | */ 16 | case timedOut 17 | } 18 | 19 | /** 20 | This class is designed to make it possible to use asynchronous contexts in a synchronous environment. 21 | */ 22 | public final class Portal { 23 | fileprivate var result: Result? = .none 24 | private let semaphore: DispatchSemaphore 25 | private let lock = NSLock() 26 | 27 | fileprivate init(_ semaphore: DispatchSemaphore) { 28 | self.semaphore = semaphore 29 | } 30 | 31 | /** 32 | Close the portal with a successful result 33 | */ 34 | public func close(with value: T) { 35 | lock.locked { 36 | guard result == nil else { return } 37 | result = .success(value) 38 | semaphore.signal() 39 | } 40 | } 41 | 42 | /** 43 | Close the portal with an appropriate error 44 | */ 45 | public func close(with error: Error) { 46 | lock.locked { 47 | guard result == nil else { return } 48 | result = .failure(error) 49 | semaphore.signal() 50 | } 51 | } 52 | 53 | /** 54 | Dismiss the portal throwing a notClosed error. 55 | */ 56 | public func destroy() { 57 | semaphore.signal() 58 | } 59 | } 60 | 61 | extension Portal { 62 | /** 63 | This function is used to enter an asynchronous supported context with a portal 64 | object that can be used to complete a given operation. 65 | 66 | timeout in SECONDS 67 | 68 | let value = try Portal.open { portal in 69 | // .. do whatever necessary passing around `portal` object 70 | // eventually call 71 | 72 | portal.close(with: 42) 73 | 74 | // or 75 | 76 | portal.close(with: errorSignifyingFailure) 77 | } 78 | 79 | - warning: Calling close on a `portal` multiple times will have no effect. 80 | */ 81 | public static func open( 82 | timeout: Double = (60 * 60), 83 | _ handler: @escaping (Portal) throws -> Void 84 | ) throws -> T { 85 | let semaphore = DispatchSemaphore(value: 0) 86 | let portal = Portal(semaphore) 87 | background { 88 | do { 89 | try handler(portal) 90 | } catch { 91 | portal.close(with: error) 92 | } 93 | } 94 | let waitResult = semaphore.wait(timeout: timeout) 95 | switch waitResult { 96 | case .success: 97 | guard let result = portal.result else { throw PortalError.notClosed } 98 | return try result.extract() 99 | case .timedOut: 100 | throw PortalError.timedOut 101 | } 102 | } 103 | } 104 | 105 | extension Portal { 106 | /** 107 | Execute timeout operations 108 | */ 109 | static func timeout(_ timeout: Double, operation: @escaping () throws -> T) throws -> T { 110 | return try Portal.open(timeout: timeout) { portal in 111 | let value = try operation() 112 | portal.close(with: value) 113 | } 114 | } 115 | } 116 | 117 | extension PortalError { 118 | public var identifier: String { 119 | return rawValue 120 | } 121 | 122 | public var reason: String { 123 | switch self { 124 | case .notClosed: 125 | return "the portal finished, but was somehow not properly closed" 126 | case .timedOut: 127 | return "the portal timed out before it could finish its operation" 128 | } 129 | } 130 | 131 | public var possibleCauses: [String] { 132 | return [ 133 | "user forgot to call `portal.close(with: )`" 134 | ] 135 | } 136 | 137 | public var suggestedFixes: [String] { 138 | return [ 139 | "ensure the timeout length is adequate for required operation time", 140 | "make sure that `portal.close(with: )` is being called with an error or valid value" 141 | ] 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/RFC1123.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct RFC1123 { 4 | public static let shared = RFC1123() 5 | public let formatter: DateFormatter 6 | 7 | public init() { 8 | let formatter = DateFormatter() 9 | formatter.timeZone = TimeZone(secondsFromGMT: 0) 10 | formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z" 11 | self.formatter = formatter 12 | } 13 | } 14 | 15 | extension Date { 16 | public var rfc1123: String { 17 | return RFC1123.shared.formatter.string(from: self) 18 | } 19 | 20 | public init?(rfc1123: String) { 21 | guard let date = RFC1123.shared.formatter.date(from: rfc1123) else { 22 | return nil 23 | } 24 | 25 | self = date 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Result.swift: -------------------------------------------------------------------------------- 1 | public enum Result { 2 | case success(T) 3 | case failure(Error) 4 | } 5 | 6 | extension Result { 7 | public func extract() throws -> T { 8 | switch self { 9 | case .success(let val): 10 | return val 11 | case .failure(let e): 12 | throw e 13 | } 14 | } 15 | } 16 | 17 | extension Result { 18 | public var value: T? { 19 | guard case let .success(val) = self else { return nil } 20 | return val 21 | } 22 | 23 | public var error: Error? { 24 | guard case let .failure(err) = self else { return nil } 25 | return err 26 | } 27 | } 28 | 29 | extension Result { 30 | public var succeeded: Bool { 31 | return value != nil 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Semaphore.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | extension DispatchSemaphore { 4 | /** 5 | Wait for a specified time in SECONDS 6 | timeout if necessary 7 | */ 8 | public func wait(timeout: Double) -> DispatchTimeoutResult { 9 | let time = DispatchTime(secondsFromNow: timeout) 10 | return wait(timeout: time) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/Sequence.swift: -------------------------------------------------------------------------------- 1 | extension Sequence { 2 | /** 3 | Convert the given sequence to its array representation 4 | */ 5 | public var array: [Iterator.Element] { 6 | return Array(self) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/StaticDataBuffer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | This class is intended to make interacting with and 3 | iterating through a static data buffer a simpler process. 4 | 5 | It's intent is to be subclassed so the next 6 | function can be overridden with further rules. 7 | */ 8 | open class StaticDataBuffer { 9 | private var localBuffer: [Byte] = [] 10 | private var buffer: AnyIterator 11 | 12 | public init(bytes: S) where S.Iterator.Element == Byte { 13 | var any = bytes.makeIterator() 14 | self.buffer = AnyIterator { return any.next() } 15 | } 16 | 17 | // MARK: Next 18 | 19 | open func next() throws -> Byte? { 20 | /* 21 | Local buffer is used to maintain last bytes 22 | while still interacting w/ byte buffer. 23 | */ 24 | guard localBuffer.isEmpty else { 25 | return localBuffer.removeFirst() 26 | } 27 | return buffer.next() 28 | } 29 | 30 | public func next(matchesAny: Byte...) throws -> Bool { 31 | guard let next = try next() else { return false } 32 | returnToBuffer(next) 33 | return matchesAny.contains(next) 34 | } 35 | 36 | public func next(matches: (Byte) throws -> Bool) throws -> Bool { 37 | guard let next = try next() else { return false } 38 | returnToBuffer(next) 39 | return try matches(next) 40 | } 41 | 42 | // MARK: 43 | 44 | public func returnToBuffer(_ byte: Byte) { 45 | returnToBuffer([byte]) 46 | } 47 | 48 | public func returnToBuffer(_ bytes: [Byte]) { 49 | localBuffer.append(contentsOf: bytes) 50 | } 51 | 52 | // MARK: Discard Extranneous Tokens 53 | 54 | public func discardNext(_ count: Int) throws { 55 | _ = try collect(next: count) 56 | } 57 | 58 | // MARK: Check Tokens 59 | 60 | public func checkLeadingBuffer(matches: Byte...) throws -> Bool { 61 | return try checkLeadingBuffer(matches: matches) 62 | } 63 | 64 | public func checkLeadingBuffer(matches: [Byte]) throws -> Bool { 65 | let leading = try collect(next: matches.count) 66 | returnToBuffer(leading) 67 | return leading == matches 68 | } 69 | 70 | // MARK: Collection 71 | 72 | public func collect(next count: Int) throws -> [Byte] { 73 | guard count > 0 else { return [] } 74 | 75 | var body: [Byte] = [] 76 | try (1...count).forEach { _ in 77 | guard let next = try next() else { return } 78 | body.append(next) 79 | } 80 | return body 81 | } 82 | 83 | /** 84 | Collect until delimitters are reached, optionally convert 85 | specific bytes along the way 86 | 87 | When in Query segment, `+` should be interpreted as ` ` (space), 88 | not sure useful outside of that point. 89 | */ 90 | public func collect( 91 | until delimitters: Byte..., 92 | convertIfNecessary: (Byte) -> Byte = { $0 } 93 | ) throws -> [Byte] { 94 | var collected: [Byte] = [] 95 | while let next = try next() { 96 | if delimitters.contains(next) { 97 | // If the delimitter is also a token that identifies 98 | // a particular section of the URI 99 | // then we may want to return that byte to the buffer 100 | returnToBuffer(next) 101 | break 102 | } 103 | 104 | let converted = convertIfNecessary(next) 105 | collected.append(converted) 106 | } 107 | return collected 108 | } 109 | 110 | /** 111 | Collect any remaining bytes until buffer is empty 112 | */ 113 | public func collectRemaining() throws -> [Byte] { 114 | var complete: [Byte] = [] 115 | while let next = try next() { 116 | complete.append(next) 117 | } 118 | return complete 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/String+CaseInsensitiveCompare.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | /** 3 | Case insensitive comparison on argument 4 | */ 5 | public func equals(caseInsensitive: String) -> Bool { 6 | return lowercased() == caseInsensitive.lowercased() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/String+Polymorphic.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | /// Determines whether or not the `String` is null. 3 | /// Returns `true` if the `String` is equal to `"null"`. 4 | public var isNull: Bool { 5 | return self.lowercased() == "null" 6 | } 7 | 8 | /// Attempts to convert the String to a `Bool`. 9 | /// The conversion **may** succeed if the `String` 10 | /// has a truthy/falsey value like `"yes"` or `"false"` 11 | /// All others will always return `nil`. 12 | public var bool: Bool? { 13 | switch lowercased() { 14 | case "y", "1", "yes", "t", "true", "on": 15 | return true 16 | case "n", "0", "no", "f", "false", "off": 17 | return false 18 | default: 19 | return nil 20 | } 21 | } 22 | 23 | /// Attempts to convert the `String` to a `Float`. 24 | /// The conversion uses the `Float(_: String)` initializer. 25 | public var float: Float? { 26 | return Float(self) 27 | } 28 | 29 | /// Attempts to convert the `String` to a `Double`. 30 | /// The conversion uses the `Double(_: String)` initializer. 31 | public var double: Double? { 32 | return Double(self) 33 | } 34 | 35 | /// Attempts to convert the `String` to a `Int`. 36 | /// The conversion uses the `Int(_: String)` initializer. 37 | public var int: Int? { 38 | return Int(self) 39 | } 40 | 41 | /// Attempts to convert the `String` to a `UInt`. 42 | /// The conversion uses the `UInt(_: String)` initializer. 43 | public var uint: UInt? { 44 | return UInt(self) 45 | } 46 | 47 | /// Attempts to convert the `String` to a `String`. 48 | /// This always works. 49 | public var string: String { 50 | return self 51 | } 52 | 53 | /// Converts the string to a UTF8 array of bytes. 54 | public var bytes: [UInt8] { 55 | return [UInt8](self.utf8) 56 | } 57 | } 58 | 59 | extension String { 60 | /// Attempts to convert the `String` to an `Array`. 61 | /// Comma separated items will be split into 62 | /// multiple entries. 63 | public func commaSeparatedArray() -> [String] { 64 | return characters 65 | .split(separator: ",") 66 | .map { String($0) } 67 | .map { $0.trimmedWhitespace() } 68 | } 69 | } 70 | 71 | extension String { 72 | fileprivate func trimmedWhitespace() -> String { 73 | var characters = self.characters 74 | 75 | while characters.first?.isWhitespace == true { 76 | characters.removeFirst() 77 | } 78 | while characters.last?.isWhitespace == true { 79 | characters.removeLast() 80 | } 81 | 82 | return String(characters) 83 | } 84 | } 85 | 86 | extension Character { 87 | fileprivate var isWhitespace: Bool { 88 | switch self { 89 | case " ", "\t", "\n", "\r": 90 | return true 91 | default: 92 | return false 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/String.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | /** 3 | Ensures a string has a strailing suffix w/o duplicating 4 | 5 | "hello.jpg".finished(with: ".jpg") 6 | // == 'hello.jpg' 7 | 8 | "hello".finished(with: ".jpg") 9 | // == 'hello.jpg' 10 | */ 11 | public func finished(with end: String) -> String { 12 | guard !self.hasSuffix(end) else { return self } 13 | return self + end 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/Core/WorkingDirectory.swift: -------------------------------------------------------------------------------- 1 | #if !COCOAPODS 2 | import libc 3 | #endif 4 | 5 | /// This function will attempt to get the current 6 | /// working directory of the application 7 | public func workingDirectory() -> String { 8 | let fileBasedWorkDir: String? 9 | 10 | #if Xcode 11 | // attempt to find working directory through #file 12 | let file = #file 13 | 14 | if file.contains(".build") { 15 | // most dependencies are in `./.build/` 16 | fileBasedWorkDir = file.components(separatedBy: "/.build").first 17 | } else if file.contains("Packages") { 18 | // when editing a dependency, it is in `./Packages/` 19 | fileBasedWorkDir = file.components(separatedBy: "/Packages").first 20 | } else { 21 | // when dealing with current repository, file is in `./Sources/` 22 | fileBasedWorkDir = file.components(separatedBy: "/Sources").first 23 | } 24 | #else 25 | fileBasedWorkDir = nil 26 | #endif 27 | 28 | let workDir: String 29 | if let fileBasedWorkDir = fileBasedWorkDir { 30 | workDir = fileBasedWorkDir 31 | } else { 32 | // get actual working directory 33 | let cwd = getcwd(nil, Int(PATH_MAX)) 34 | defer { 35 | free(cwd) 36 | } 37 | 38 | if let cwd = cwd, let string = String(validatingUTF8: cwd) { 39 | workDir = string 40 | } else { 41 | workDir = "./" 42 | } 43 | } 44 | 45 | return workDir.finished(with: "/") 46 | } 47 | -------------------------------------------------------------------------------- /Demo/Pods/Core/Sources/libc/libc.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | @_exported import Glibc 3 | #else 4 | @_exported import Darwin.C 5 | #endif 6 | -------------------------------------------------------------------------------- /Demo/Pods/Local Podspecs/Permission.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Permission", 3 | "version": "1.0.3", 4 | "summary": "An elegant permission manager written in swift", 5 | "homepage": "https://github.com/lixiang1994/Permission", 6 | "license": { 7 | "type": "MIT", 8 | "file": "LICENSE" 9 | }, 10 | "authors": { 11 | "LEE": "18611401994@163.com" 12 | }, 13 | "platforms": { 14 | "ios": "9.0" 15 | }, 16 | "source": { 17 | "git": "https://github.com/lixiang1994/Permission.git", 18 | "tag": "1.0.3" 19 | }, 20 | "source_files": "Sources/**/*.swift", 21 | "requires_arc": true, 22 | "frameworks": [ 23 | "UIKit", 24 | "Foundation" 25 | ], 26 | "swift_versions": "5.0", 27 | "default_subspecs": "Core", 28 | "subspecs": [ 29 | { 30 | "name": "Core", 31 | "source_files": "Sources/Core/*.swift" 32 | }, 33 | { 34 | "name": "Alert", 35 | "dependencies": { 36 | "Permission/Core": [ 37 | 38 | ] 39 | }, 40 | "source_files": "Sources/Alert/*.swift" 41 | }, 42 | { 43 | "name": "Camera", 44 | "dependencies": { 45 | "Permission/Core": [ 46 | 47 | ] 48 | }, 49 | "source_files": "Sources/Managers/Permission+Camera.swift", 50 | "weak_frameworks": "AVFoundation" 51 | }, 52 | { 53 | "name": "Photos", 54 | "dependencies": { 55 | "Permission/Core": [ 56 | 57 | ] 58 | }, 59 | "source_files": "Sources/Managers/Permission+Photos.swift", 60 | "weak_frameworks": "Photos" 61 | }, 62 | { 63 | "name": "Event", 64 | "dependencies": { 65 | "Permission/Core": [ 66 | 67 | ] 68 | }, 69 | "source_files": "Sources/Managers/Permission+Event.swift", 70 | "weak_frameworks": "EventKit" 71 | }, 72 | { 73 | "name": "Contacts", 74 | "dependencies": { 75 | "Permission/Core": [ 76 | 77 | ] 78 | }, 79 | "source_files": "Sources/Managers/Permission+Contacts.swift", 80 | "weak_frameworks": "Contacts" 81 | }, 82 | { 83 | "name": "Speech", 84 | "dependencies": { 85 | "Permission/Core": [ 86 | 87 | ] 88 | }, 89 | "source_files": "Sources/Managers/Permission+Speech.swift", 90 | "weak_frameworks": "Speech" 91 | }, 92 | { 93 | "name": "Motion", 94 | "dependencies": { 95 | "Permission/Core": [ 96 | 97 | ] 98 | }, 99 | "source_files": "Sources/Managers/Permission+Motion.swift", 100 | "weak_frameworks": "CoreMotion" 101 | }, 102 | { 103 | "name": "Media", 104 | "dependencies": { 105 | "Permission/Core": [ 106 | 107 | ] 108 | }, 109 | "source_files": "Sources/Managers/Permission+Media.swift", 110 | "weak_frameworks": "MediaPlayer" 111 | }, 112 | { 113 | "name": "Siri", 114 | "dependencies": { 115 | "Permission/Core": [ 116 | 117 | ] 118 | }, 119 | "source_files": "Sources/Managers/Permission+Siri.swift", 120 | "weak_frameworks": "Intents" 121 | }, 122 | { 123 | "name": "Location", 124 | "dependencies": { 125 | "Permission/Core": [ 126 | 127 | ] 128 | }, 129 | "source_files": "Sources/Managers/Permission+Location.swift", 130 | "weak_frameworks": "CoreLocation" 131 | }, 132 | { 133 | "name": "Notification", 134 | "dependencies": { 135 | "Permission/Core": [ 136 | 137 | ] 138 | }, 139 | "source_files": "Sources/Managers/Permission+Notification.swift", 140 | "weak_frameworks": "UserNotifications" 141 | } 142 | ], 143 | "swift_version": "5.0" 144 | } 145 | -------------------------------------------------------------------------------- /Demo/Pods/Local Podspecs/PermissionKit.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PermissionKit", 3 | "version": "1.6.0", 4 | "summary": "An elegant permission manager written in swift", 5 | "homepage": "https://github.com/lixiang1994/PermissionKit", 6 | "license": { 7 | "type": "MIT", 8 | "file": "LICENSE" 9 | }, 10 | "authors": { 11 | "LEE": "18611401994@163.com" 12 | }, 13 | "platforms": { 14 | "ios": "9.0" 15 | }, 16 | "source": { 17 | "git": "https://github.com/lixiang1994/PermissionKit.git", 18 | "tag": "1.6.0" 19 | }, 20 | "source_files": "Sources/**/*.swift", 21 | "requires_arc": true, 22 | "frameworks": [ 23 | "UIKit", 24 | "Foundation" 25 | ], 26 | "swift_versions": "5.0", 27 | "default_subspecs": [ 28 | "Core", 29 | "Camera", 30 | "Photos", 31 | "Event", 32 | "Contacts", 33 | "Speech", 34 | "Motion", 35 | "Media", 36 | "Siri", 37 | "Location", 38 | "Notification", 39 | "Tracking", 40 | "Bluetooth" 41 | ], 42 | "subspecs": [ 43 | { 44 | "name": "Core", 45 | "source_files": "Sources/Core/*.swift", 46 | "dependencies": { 47 | "PermissionKit/Privacy": [ 48 | 49 | ] 50 | } 51 | }, 52 | { 53 | "name": "Alert", 54 | "dependencies": { 55 | "PermissionKit/Core": [ 56 | 57 | ] 58 | }, 59 | "source_files": "Sources/Alert/*.swift" 60 | }, 61 | { 62 | "name": "Camera", 63 | "dependencies": { 64 | "PermissionKit/Core": [ 65 | 66 | ] 67 | }, 68 | "source_files": "Sources/Managers/Permission.Camera.swift", 69 | "weak_frameworks": "AVFoundation" 70 | }, 71 | { 72 | "name": "Photos", 73 | "dependencies": { 74 | "PermissionKit/Core": [ 75 | 76 | ] 77 | }, 78 | "source_files": "Sources/Managers/Permission.Photos.swift", 79 | "weak_frameworks": "Photos" 80 | }, 81 | { 82 | "name": "Event", 83 | "dependencies": { 84 | "PermissionKit/Core": [ 85 | 86 | ] 87 | }, 88 | "source_files": "Sources/Managers/Permission.Event.swift", 89 | "weak_frameworks": "EventKit" 90 | }, 91 | { 92 | "name": "Contacts", 93 | "dependencies": { 94 | "PermissionKit/Core": [ 95 | 96 | ] 97 | }, 98 | "source_files": "Sources/Managers/Permission.Contacts.swift", 99 | "weak_frameworks": "Contacts" 100 | }, 101 | { 102 | "name": "Speech", 103 | "dependencies": { 104 | "PermissionKit/Core": [ 105 | 106 | ] 107 | }, 108 | "source_files": "Sources/Managers/Permission.Speech.swift", 109 | "weak_frameworks": "Speech" 110 | }, 111 | { 112 | "name": "Motion", 113 | "dependencies": { 114 | "PermissionKit/Core": [ 115 | 116 | ] 117 | }, 118 | "source_files": "Sources/Managers/Permission.Motion.swift", 119 | "weak_frameworks": "CoreMotion" 120 | }, 121 | { 122 | "name": "Media", 123 | "dependencies": { 124 | "PermissionKit/Core": [ 125 | 126 | ] 127 | }, 128 | "source_files": "Sources/Managers/Permission.Media.swift", 129 | "weak_frameworks": "MediaPlayer" 130 | }, 131 | { 132 | "name": "Siri", 133 | "dependencies": { 134 | "PermissionKit/Core": [ 135 | 136 | ] 137 | }, 138 | "source_files": "Sources/Managers/Permission.Siri.swift", 139 | "weak_frameworks": "Intents" 140 | }, 141 | { 142 | "name": "Location", 143 | "dependencies": { 144 | "PermissionKit/Core": [ 145 | 146 | ] 147 | }, 148 | "source_files": "Sources/Managers/Permission.Location.swift", 149 | "weak_frameworks": "CoreLocation" 150 | }, 151 | { 152 | "name": "Notification", 153 | "dependencies": { 154 | "PermissionKit/Core": [ 155 | 156 | ] 157 | }, 158 | "source_files": "Sources/Managers/Permission.Notification.swift", 159 | "weak_frameworks": "UserNotifications" 160 | }, 161 | { 162 | "name": "Tracking", 163 | "dependencies": { 164 | "PermissionKit/Core": [ 165 | 166 | ] 167 | }, 168 | "source_files": "Sources/Managers/Permission.Tracking.swift", 169 | "weak_frameworks": [ 170 | "AppTrackingTransparency", 171 | "AdSupport" 172 | ] 173 | }, 174 | { 175 | "name": "Bluetooth", 176 | "dependencies": { 177 | "PermissionKit/Core": [ 178 | 179 | ] 180 | }, 181 | "source_files": "Sources/Managers/Permission.Bluetooth.swift", 182 | "weak_frameworks": "CoreBluetooth" 183 | }, 184 | { 185 | "name": "Privacy", 186 | "resource_bundles": { 187 | "Privacy": "Sources/PrivacyInfo.xcprivacy" 188 | } 189 | } 190 | ], 191 | "swift_version": "5.0" 192 | } 193 | -------------------------------------------------------------------------------- /Demo/Pods/Local Podspecs/Spring.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Spring", 3 | "version": "1.1.5", 4 | "summary": "一个简单易用的iOS链式动画扩展库 Swift", 5 | "homepage": "https://github.com/lixiang1994/Spring", 6 | "license": { 7 | "type": "MIT", 8 | "file": "LICENSE" 9 | }, 10 | "authors": { 11 | "LEE": "18611401994@163.com" 12 | }, 13 | "platforms": { 14 | "ios": "9.0" 15 | }, 16 | "source": { 17 | "git": "https://github.com/lixiang1994/Spring.git", 18 | "tag": "1.1.5" 19 | }, 20 | "source_files": "Sources/**/*.swift", 21 | "requires_arc": true, 22 | "frameworks": [ 23 | "UIKit", 24 | "Foundation" 25 | ], 26 | "swift_version": "5.0" 27 | } 28 | -------------------------------------------------------------------------------- /Demo/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - PermissionKit (1.6.0): 3 | - PermissionKit/Bluetooth (= 1.6.0) 4 | - PermissionKit/Camera (= 1.6.0) 5 | - PermissionKit/Contacts (= 1.6.0) 6 | - PermissionKit/Core (= 1.6.0) 7 | - PermissionKit/Event (= 1.6.0) 8 | - PermissionKit/Location (= 1.6.0) 9 | - PermissionKit/Media (= 1.6.0) 10 | - PermissionKit/Motion (= 1.6.0) 11 | - PermissionKit/Notification (= 1.6.0) 12 | - PermissionKit/Photos (= 1.6.0) 13 | - PermissionKit/Siri (= 1.6.0) 14 | - PermissionKit/Speech (= 1.6.0) 15 | - PermissionKit/Tracking (= 1.6.0) 16 | - PermissionKit/Bluetooth (1.6.0): 17 | - PermissionKit/Core 18 | - PermissionKit/Camera (1.6.0): 19 | - PermissionKit/Core 20 | - PermissionKit/Contacts (1.6.0): 21 | - PermissionKit/Core 22 | - PermissionKit/Core (1.6.0): 23 | - PermissionKit/Privacy 24 | - PermissionKit/Event (1.6.0): 25 | - PermissionKit/Core 26 | - PermissionKit/Location (1.6.0): 27 | - PermissionKit/Core 28 | - PermissionKit/Media (1.6.0): 29 | - PermissionKit/Core 30 | - PermissionKit/Motion (1.6.0): 31 | - PermissionKit/Core 32 | - PermissionKit/Notification (1.6.0): 33 | - PermissionKit/Core 34 | - PermissionKit/Photos (1.6.0): 35 | - PermissionKit/Core 36 | - PermissionKit/Privacy (1.6.0) 37 | - PermissionKit/Siri (1.6.0): 38 | - PermissionKit/Core 39 | - PermissionKit/Speech (1.6.0): 40 | - PermissionKit/Core 41 | - PermissionKit/Tracking (1.6.0): 42 | - PermissionKit/Core 43 | 44 | DEPENDENCIES: 45 | - PermissionKit (from `../`) 46 | 47 | EXTERNAL SOURCES: 48 | PermissionKit: 49 | :path: "../" 50 | 51 | SPEC CHECKSUMS: 52 | PermissionKit: afc9d0a834126aae7c03650ef519fe8793d44492 53 | 54 | PODFILE CHECKSUM: c700c41c391b70b5dcd6e1fce8cd14bae906b944 55 | 56 | COCOAPODS: 1.15.2 57 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/PermissionKit-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 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.6.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/PermissionKit-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_PermissionKit : NSObject 3 | @end 4 | @implementation PodsDummy_PermissionKit 5 | @end 6 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/PermissionKit-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/PermissionKit-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double PermissionKitVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char PermissionKitVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/PermissionKit.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "UIKit" -weak_framework "AVFoundation" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Contacts" -weak_framework "CoreBluetooth" -weak_framework "CoreLocation" -weak_framework "CoreMotion" -weak_framework "EventKit" -weak_framework "Intents" -weak_framework "MediaPlayer" -weak_framework "Photos" -weak_framework "Speech" -weak_framework "UserNotifications" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/PermissionKit.modulemap: -------------------------------------------------------------------------------- 1 | framework module PermissionKit { 2 | umbrella header "PermissionKit-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/PermissionKit.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "UIKit" -weak_framework "AVFoundation" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Contacts" -weak_framework "CoreBluetooth" -weak_framework "CoreLocation" -weak_framework "CoreMotion" -weak_framework "EventKit" -weak_framework "Intents" -weak_framework "MediaPlayer" -weak_framework "Photos" -weak_framework "Speech" -weak_framework "UserNotifications" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/PermissionKit/ResourceBundle-Privacy-PermissionKit-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.6.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 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.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## PermissionKit 5 | 6 | MIT License 7 | 8 | Copyright (c) 2019 LEE 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | MIT License 18 | 19 | Copyright (c) 2019 LEE 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | PermissionKit 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Demo : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Demo 5 | @end 6 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-frameworks-Debug-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/PermissionKit/PermissionKit.framework -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-frameworks-Debug-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PermissionKit.framework -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-frameworks-Release-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/PermissionKit/PermissionKit.framework -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-frameworks-Release-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PermissionKit.framework -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink -f "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/PermissionKit/PermissionKit.framework" 180 | fi 181 | if [[ "$CONFIGURATION" == "Release" ]]; then 182 | install_framework "${BUILT_PRODUCTS_DIR}/PermissionKit/PermissionKit.framework" 183 | fi 184 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 185 | wait 186 | fi 187 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_DemoVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_DemoVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit/PermissionKit.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit/PermissionKit.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit" 9 | OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "PermissionKit" -framework "UIKit" -weak_framework "AVFoundation" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Contacts" -weak_framework "CoreBluetooth" -weak_framework "CoreLocation" -weak_framework "CoreMotion" -weak_framework "EventKit" -weak_framework "Intents" -weak_framework "MediaPlayer" -weak_framework "Photos" -weak_framework "Speech" -weak_framework "UserNotifications" 10 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 11 | PODS_BUILD_DIR = ${BUILD_DIR} 12 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 13 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 14 | PODS_ROOT = ${SRCROOT}/Pods 15 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Demo { 2 | umbrella header "Pods-Demo-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Pods/Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit/PermissionKit.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit/PermissionKit.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/PermissionKit" 9 | OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "PermissionKit" -framework "UIKit" -weak_framework "AVFoundation" -weak_framework "AdSupport" -weak_framework "AppTrackingTransparency" -weak_framework "Contacts" -weak_framework "CoreBluetooth" -weak_framework "CoreLocation" -weak_framework "CoreMotion" -weak_framework "EventKit" -weak_framework "Intents" -weak_framework "MediaPlayer" -weak_framework "Photos" -weak_framework "Speech" -weak_framework "UserNotifications" 10 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 11 | PODS_BUILD_DIR = ${BUILD_DIR} 12 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 13 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 14 | PODS_ROOT = ${SRCROOT}/Pods 15 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 LEE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PermissionKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'PermissionKit' 4 | s.version = '1.6.1' 5 | s.summary = 'An elegant permission manager written in swift' 6 | 7 | s.homepage = 'https://github.com/lixiang1994/PermissionKit' 8 | 9 | s.license = { :type => 'MIT', :file => 'LICENSE' } 10 | 11 | s.author = { 'LEE' => '18611401994@163.com' } 12 | 13 | s.platform = :ios, '9.0' 14 | 15 | s.source = { :git => 'https://github.com/lixiang1994/PermissionKit.git', :tag => s.version } 16 | 17 | s.source_files = 'Sources/**/*.swift' 18 | 19 | s.requires_arc = true 20 | 21 | s.frameworks = 'UIKit', 'Foundation' 22 | 23 | s.swift_version = '5.0' 24 | 25 | s.default_subspecs = 'Core', 'Camera', 'Photos', 'Event', 'Contacts', 'Speech', 'Motion', 'Media', 'Siri', 'Location', 'Notification', 'Tracking', 'Bluetooth' 26 | 27 | s.subspec 'Core' do |sub| 28 | sub.source_files = 'Sources/Core/*.swift' 29 | sub.dependency 'PermissionKit/Privacy' 30 | end 31 | 32 | s.subspec 'Alert' do |sub| 33 | sub.dependency 'PermissionKit/Core' 34 | sub.source_files = 'Sources/Alert/*.swift' 35 | end 36 | 37 | s.subspec 'Camera' do |sub| 38 | sub.dependency 'PermissionKit/Core' 39 | sub.source_files = 'Sources/Managers/Permission.Camera.swift' 40 | sub.weak_framework = 'AVFoundation' 41 | end 42 | 43 | s.subspec 'Photos' do |sub| 44 | sub.dependency 'PermissionKit/Core' 45 | sub.source_files = 'Sources/Managers/Permission.Photos.swift' 46 | sub.weak_framework = 'Photos' 47 | end 48 | 49 | s.subspec 'Event' do |sub| 50 | sub.dependency 'PermissionKit/Core' 51 | sub.source_files = 'Sources/Managers/Permission.Event.swift' 52 | sub.weak_framework = 'EventKit' 53 | end 54 | 55 | s.subspec 'Contacts' do |sub| 56 | sub.dependency 'PermissionKit/Core' 57 | sub.source_files = 'Sources/Managers/Permission.Contacts.swift' 58 | sub.weak_framework = 'Contacts' 59 | end 60 | 61 | s.subspec 'Speech' do |sub| 62 | sub.dependency 'PermissionKit/Core' 63 | sub.source_files = 'Sources/Managers/Permission.Speech.swift' 64 | sub.weak_framework = 'Speech' 65 | end 66 | 67 | s.subspec 'Motion' do |sub| 68 | sub.dependency 'PermissionKit/Core' 69 | sub.source_files = 'Sources/Managers/Permission.Motion.swift' 70 | sub.weak_framework = 'CoreMotion' 71 | end 72 | 73 | s.subspec 'Media' do |sub| 74 | sub.dependency 'PermissionKit/Core' 75 | sub.source_files = 'Sources/Managers/Permission.Media.swift' 76 | sub.weak_framework = 'MediaPlayer' 77 | end 78 | 79 | s.subspec 'Siri' do |sub| 80 | sub.dependency 'PermissionKit/Core' 81 | sub.source_files = 'Sources/Managers/Permission.Siri.swift' 82 | sub.weak_frameworks = 'Intents' 83 | end 84 | 85 | s.subspec 'Location' do |sub| 86 | sub.dependency 'PermissionKit/Core' 87 | sub.source_files = 'Sources/Managers/Permission.Location.swift' 88 | sub.weak_framework = 'CoreLocation' 89 | end 90 | 91 | s.subspec 'Notification' do |sub| 92 | sub.dependency 'PermissionKit/Core' 93 | sub.source_files = 'Sources/Managers/Permission.Notification.swift' 94 | sub.weak_framework = 'UserNotifications' 95 | end 96 | 97 | s.subspec 'Tracking' do |sub| 98 | sub.dependency 'PermissionKit/Core' 99 | sub.source_files = 'Sources/Managers/Permission.Tracking.swift' 100 | sub.weak_frameworks = 'AppTrackingTransparency', 'AdSupport' 101 | end 102 | 103 | s.subspec 'Bluetooth' do |sub| 104 | sub.dependency 'PermissionKit/Core' 105 | sub.source_files = 'Sources/Managers/Permission.Bluetooth.swift' 106 | sub.weak_frameworks = 'CoreBluetooth' 107 | end 108 | 109 | s.subspec 'Privacy' do |sub| 110 | sub.resource_bundles = { 111 | s.name => 'Sources/PrivacyInfo.xcprivacy' 112 | } 113 | end 114 | 115 | end 116 | -------------------------------------------------------------------------------- /PermissionKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PermissionKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PermissionKit.xcodeproj/xcshareddata/xcschemes/PermissionKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PermissionKit 2 | Permission Manager 3 | 4 | ![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) 5 | 6 | ## [天朝子民🇨🇳](README_CN.md) 7 | 8 | ## Features 9 | 10 | - [x] Camera. 11 | - [x] Photos. 12 | - [x] Contacts. 13 | - [x] Calendar. 14 | - [x] Reminder. 15 | - [x] Media Library. 16 | - [x] Microphone. 17 | - [x] Siri. 18 | - [x] Motion. 19 | - [x] Speech. 20 | - [x] Location. 21 | - [x] Notification. 22 | - [x] AppTracking. 23 | - [x] Bluetooth. 24 | 25 | ## Installation 26 | 27 | **CocoaPods - Podfile** 28 | 29 | ```ruby 30 | source 'https://github.com/lixiang1994/Specs' 31 | 32 | // All 33 | pod 'PermissionKit' 34 | 35 | // Add separately 36 | pod 'PermissionKit/Camera' 37 | pod 'PermissionKit/Photos' 38 | pod 'PermissionKit/Contacts' 39 | pod 'PermissionKit/Event' 40 | pod 'PermissionKit/Motion' 41 | pod 'PermissionKit/Speech' 42 | pod 'PermissionKit/Media' 43 | pod 'PermissionKit/Siri' 44 | pod 'PermissionKit/Location' 45 | pod 'PermissionKit/Notification' 46 | pod 'PermissionKit/Tracking' 47 | pod 'PermissionKit/Bluetooth' 48 | ``` 49 | 50 | **Carthage - Cartfile** 51 | 52 | ```ruby 53 | github "lixiang1994/PermissionKit" 54 | ``` 55 | 56 | ## Usage 57 | 58 | First make sure to import the framework: 59 | 60 | ```swift 61 | import PermissionKit 62 | ``` 63 | 64 | Here are some usage examples. All devices are also available as simulators: 65 | 66 | 67 | ### Property 68 | ```swift 69 | Provider.camera.isAuthorized 70 | 71 | Provider.photos.isAuthorized 72 | 73 | Provider.XXXXXX.isAuthorized 74 | ``` 75 | 76 | ### Functions 77 | ```swift 78 | Provider.camera.request { (result) in 79 | print("isAuthorized: \(result)") 80 | } 81 | 82 | Provider.XXXXXX.request { (result) in 83 | print("isAuthorized: \(result)") 84 | } 85 | ``` 86 | 87 | ### Alert 88 | 89 | #### Protocol 90 | ```swift 91 | public protocol PermissionAlertable { 92 | 93 | init(_ source: PermissionAlertContentSource) 94 | 95 | func show(_ status: AlertStatus, with сompletion: @escaping (Bool) -> Void) 96 | } 97 | ``` 98 | #### SystemAlert based `UIAlertController` 99 | ``` 100 | let alert = SystemAlert(ChineseAlertContent()) 101 | Provider.camera.request(alert) { result in 102 | /* ... */ 103 | } 104 | ``` 105 | CustomAlert need to implement `PermissionAlertable` protocol 106 | 107 | ## Contributing 108 | 109 | If you have the need for a specific feature that you want implemented or if you experienced a bug, please open an issue. 110 | If you extended the functionality of PermissionKit yourself and want others to use it too, please submit a pull request. 111 | 112 | 113 | ## License 114 | 115 | PermissionKit is under MIT license. See the [LICENSE](LICENSE) file for more info. 116 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 2 | # PermissionKit 3 | Permission Manager 4 | 5 | ![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) 6 | 7 | 8 | ## 特性 9 | 10 | - [x] 相机. 11 | - [x] 相册. 12 | - [x] 联系人. 13 | - [x] 日历. 14 | - [x] 提醒. 15 | - [x] 媒体库. 16 | - [x] 麦克风. 17 | - [x] Siri. 18 | - [x] 动作. 19 | - [x] 语音. 20 | - [x] 定位. 21 | - [x] 通知. 22 | - [x] 应用广告追踪. 23 | - [x] 蓝牙. 24 | 25 | ## 安装 26 | 27 | **CocoaPods - Podfile** 28 | 29 | ```ruby 30 | source 'https://github.com/lixiang1994/Specs' 31 | 32 | // 完整 33 | pod 'PermissionKit' 34 | 35 | // 单独添加所需 36 | pod 'PermissionKit/Camera' 37 | pod 'PermissionKit/Photos' 38 | pod 'PermissionKit/Contacts' 39 | pod 'PermissionKit/Event' 40 | pod 'PermissionKit/Motion' 41 | pod 'PermissionKit/Speech' 42 | pod 'PermissionKit/Media' 43 | pod 'PermissionKit/Siri' 44 | pod 'PermissionKit/Location' 45 | pod 'PermissionKit/Notification' 46 | pod 'PermissionKit/Tracking' 47 | pod 'PermissionKit/Bluetooth' 48 | ``` 49 | 50 | **Carthage - Cartfile** 51 | 52 | ```ruby 53 | github "lixiang1994/PermissionKit" 54 | ``` 55 | 56 | ## 使用 57 | 58 | 首先导入: 59 | 60 | ```swift 61 | import PermissionKit 62 | ``` 63 | 64 | 下面是一些简单示例. 支持所有设备和模拟器: 65 | 66 | ### 属性 67 | ```swift 68 | Provider.camera.isAuthorized 69 | 70 | Provider.photos.isAuthorized 71 | 72 | Provider.XXXXXX.isAuthorized 73 | ``` 74 | 75 | ### 方法 76 | ```swift 77 | Provider.camera.request { (result) in 78 | print("isAuthorized: \(result)") 79 | } 80 | 81 | Provider.XXXXXX.request { (result) in 82 | print("isAuthorized: \(result)") 83 | } 84 | ``` 85 | 86 | ### Alert 87 | 88 | #### 协议 89 | ```swift 90 | public protocol PermissionAlertable { 91 | 92 | init(_ source: PermissionAlertContentSource) 93 | 94 | func show(_ status: AlertStatus, with сompletion: @escaping (Bool) -> Void) 95 | } 96 | ``` 97 | #### 系统Alert 基于 `UIAlertController` 98 | ``` 99 | let alert = SystemAlert(ChineseAlertContent()) 100 | Provider.camera.request(alert) { result in 101 | /* ... */ 102 | } 103 | ``` 104 | 自定义Alert需要实现 `PermissionAlertable` 协议 105 | 106 | 107 | ## 贡献 108 | 109 | 如果你需要实现特定功能或遇到错误,请打开issue。 如果你自己扩展了 PermissionKit 的功能并希望其他人也使用它,请提交拉取请求。 110 | 111 | 112 | ## 协议 113 | 114 | PermissionKit 使用 MIT 协议. 有关更多信息,请参阅 [LICENSE](LICENSE) 文件. 115 | -------------------------------------------------------------------------------- /Sources/Alert/DefaultAlertContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultAlertContent.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | public struct DefaultAlertContent: PermissionAlertContentSource { 15 | 16 | public init() { } 17 | 18 | public func title(_ status: AlertStatus) -> String { 19 | switch status { 20 | case .prepare(let name): 21 | return "\(Bundle.main.appName) would like to access your \(name)" 22 | 23 | case .denied(let name): 24 | return "Permission for \(name) was denied" 25 | 26 | case .disabled(let name): 27 | return "\(name) is currently disabled" 28 | } 29 | } 30 | 31 | public func message(_ status: AlertStatus) -> String { 32 | switch status { 33 | case .prepare(let name): 34 | return "Please enable access to \(name)." 35 | 36 | case .denied(let name): 37 | return "Please enable access to \(name) in the Settings app." 38 | 39 | case .disabled(let name): 40 | return "Please enable access to \(name) in the Settings app." 41 | } 42 | } 43 | 44 | public func cancelAction(_ status: AlertStatus) -> String { 45 | switch status { 46 | case .prepare: 47 | return "Cancel" 48 | 49 | case .denied: 50 | return "Cancel" 51 | 52 | case .disabled: 53 | return "OK" 54 | } 55 | } 56 | 57 | public func confirmAction(_ status: AlertStatus) -> String { 58 | switch status { 59 | case .prepare: 60 | return "Confirm" 61 | 62 | case .denied: 63 | return "Settings" 64 | 65 | case .disabled: 66 | return "" 67 | } 68 | } 69 | } 70 | 71 | fileprivate extension Bundle { 72 | 73 | var appName: String { 74 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/Alert/Provider+Alert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Provider+Alert.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | /// 提示框状态 15 | /// 16 | /// - prepare: 准备 17 | /// - denied: 拒绝 18 | /// - disabled: 停用 19 | public enum AlertStatus { 20 | case prepare(String) 21 | case denied(String) 22 | case disabled(String) 23 | } 24 | 25 | public protocol PermissionAlertable { 26 | 27 | init(_ source: PermissionAlertContentSource) 28 | 29 | func show(_ status: AlertStatus, with сompletion: @escaping (Bool) -> Void) 30 | } 31 | 32 | public protocol PermissionAlertContentSource { 33 | 34 | func title(_ status: AlertStatus) -> String 35 | 36 | func message(_ status: AlertStatus) -> String 37 | 38 | func cancelAction(_ status: AlertStatus) -> String 39 | 40 | func confirmAction(_ status: AlertStatus) -> String 41 | } 42 | 43 | extension Provider { 44 | 45 | /// 请求授权 46 | /// 47 | /// - Parameters: 48 | /// - alert: 弹窗 49 | /// - сompletion: 结果回调 50 | public func request(_ alert: PermissionAlertable = SystemAlert(), 51 | with сompletion: @escaping (Bool) -> Void) { 52 | let manager = self.manager() 53 | let name = self.alias?() ?? manager.name 54 | manager.checkUsageDescriptions() 55 | switch manager.status { 56 | case .authorized: 57 | manager.request { 58 | сompletion(true) 59 | } 60 | 61 | case .denied: 62 | alert.show(.denied(name)) { result in 63 | guard result else { 64 | сompletion(false) 65 | return 66 | } 67 | DispatchQueue.main.async { 68 | сompletion(manager.status == .authorized) 69 | } 70 | } 71 | 72 | case .disabled: 73 | alert.show(.disabled(name), with: сompletion) 74 | 75 | case .notDetermined: 76 | alert.show(.prepare(name)) { result in 77 | guard result else { 78 | сompletion(false) 79 | return 80 | } 81 | manager.request { 82 | сompletion(manager.status == .authorized) 83 | } 84 | } 85 | 86 | case .invalid: 87 | сompletion(false) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/Alert/SystemAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemAlert.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import UIKit 15 | 16 | public class SystemAlert: PermissionAlertable { 17 | 18 | private let source: PermissionAlertContentSource 19 | 20 | public var isPrepare = true 21 | public var isDenied = true 22 | public var isDisabled = true 23 | 24 | required public init(_ source: PermissionAlertContentSource = DefaultAlertContent()) { 25 | self.source = source 26 | } 27 | 28 | public func show(_ status: AlertStatus, with сompletion: @escaping (Bool) -> Void) { 29 | switch status { 30 | case .prepare: 31 | showPrepare(status, сompletion) 32 | 33 | case .denied: 34 | showDenied(status, сompletion) 35 | 36 | case .disabled: 37 | showDisabled(status, сompletion) 38 | } 39 | } 40 | 41 | private func showPrepare(_ status: AlertStatus, _ сompletion: @escaping (Bool) -> Void) { 42 | guard isPrepare else { 43 | сompletion(true) 44 | return 45 | } 46 | 47 | let title = source.title(status) 48 | let message = source.message(status) 49 | let cancel = source.cancelAction(status) 50 | let confirm = source.confirmAction(status) 51 | 52 | let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) 53 | do { 54 | let action = UIAlertAction(title: cancel, style: .cancel) { _ in 55 | сompletion(false) 56 | } 57 | controller.addAction(action) 58 | } 59 | do { 60 | let action = UIAlertAction(title: confirm, style: .default) { _ in 61 | сompletion(true) 62 | } 63 | controller.addAction(action) 64 | } 65 | UIViewController.top?.present(controller, animated: true) 66 | } 67 | 68 | private func showDenied(_ status: AlertStatus, _ сompletion: @escaping (Bool) -> Void) { 69 | guard isDenied else { 70 | сompletion(false) 71 | return 72 | } 73 | 74 | let title = source.title(status) 75 | let message = source.message(status) 76 | let cancel = source.cancelAction(status) 77 | let confirm = source.confirmAction(status) 78 | 79 | let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) 80 | do { 81 | let action = UIAlertAction(title: cancel, style: .cancel) { _ in 82 | сompletion(false) 83 | } 84 | controller.addAction(action) 85 | } 86 | do { 87 | let action = UIAlertAction(title: confirm, style: .default) { _ in 88 | guard let url = URL(string: UIApplication.openSettingsURLString) else { 89 | сompletion(false) 90 | return 91 | } 92 | 93 | var object: Any? 94 | object = NotificationCenter.default.addObserver( 95 | forName: UIApplication.didBecomeActiveNotification, 96 | object: nil, 97 | queue: .main, 98 | using: { _ in 99 | NotificationCenter.default.removeObserver(object ?? 0) 100 | DispatchQueue.main.async { 101 | сompletion(true) 102 | } 103 | } 104 | ) 105 | 106 | if #available(iOS 10.0, *) { 107 | UIApplication.shared.open(url) 108 | 109 | } else { 110 | UIApplication.shared.openURL(url) 111 | } 112 | } 113 | controller.addAction(action) 114 | } 115 | UIViewController.top?.present(controller, animated: true) 116 | } 117 | 118 | private func showDisabled(_ status: AlertStatus, _ сompletion: @escaping (Bool) -> Void) { 119 | guard isDisabled else { 120 | сompletion(false) 121 | return 122 | } 123 | 124 | let title = source.title(status) 125 | let message = source.message(status) 126 | let cancel = source.cancelAction(status) 127 | 128 | let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) 129 | let action = UIAlertAction(title: cancel, style: .cancel) { _ in 130 | сompletion(false) 131 | } 132 | controller.addAction(action) 133 | UIViewController.top?.present(controller, animated: true) 134 | } 135 | } 136 | 137 | // MARK: - UIViewController 138 | fileprivate extension UIViewController { 139 | 140 | static var top: UIViewController? { 141 | let window = UIApplication.shared.windows.first { 142 | $0.rootViewController != nil && $0.isKeyWindow 143 | } 144 | return self.topMost(of: window?.rootViewController) 145 | } 146 | 147 | private static func topMost(of viewController: UIViewController?) -> UIViewController? { 148 | // presented view controller 149 | if let presentedViewController = viewController?.presentedViewController { 150 | return self.topMost(of: presentedViewController) 151 | } 152 | 153 | // UITabBarController 154 | if let tabBarController = viewController as? UITabBarController, 155 | let selectedViewController = tabBarController.selectedViewController { 156 | return self.topMost(of: selectedViewController) 157 | } 158 | 159 | // UINavigationController 160 | if let navigationController = viewController as? UINavigationController, 161 | let visibleViewController = navigationController.visibleViewController { 162 | return self.topMost(of: visibleViewController) 163 | } 164 | 165 | // UIPageController 166 | if let pageViewController = viewController as? UIPageViewController, 167 | pageViewController.viewControllers?.count == 1 { 168 | return self.topMost(of: pageViewController.viewControllers?.first) 169 | } 170 | 171 | // child view controller 172 | for subview in viewController?.view?.subviews ?? [] { 173 | if let childViewController = subview.next as? UIViewController { 174 | return self.topMost(of: childViewController) 175 | } 176 | } 177 | 178 | return viewController 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Sources/Core/Protocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Protocol.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import Foundation 15 | 16 | public protocol Permissionable { 17 | 18 | var status: PermissionStatus { get } 19 | 20 | var name: String { get } 21 | 22 | var usageDescriptions: [String] { get } 23 | 24 | func request(_ сompletion: @escaping () -> Void) 25 | } 26 | 27 | extension Permissionable { 28 | 29 | @discardableResult 30 | func checkUsageDescriptions() -> Bool { 31 | for key in usageDescriptions { 32 | guard let _ = Bundle.main.object(forInfoDictionaryKey: key) else { 33 | fatalError("⚠️ Warning - \(key) for \(name) not found in Info.plist") 34 | } 35 | } 36 | return true 37 | } 38 | } 39 | 40 | public enum PermissionStatus { 41 | case authorized 42 | case denied 43 | case disabled 44 | case notDetermined 45 | case invalid 46 | 47 | var name: String { 48 | switch self { 49 | case .authorized: return "Authorized" 50 | case .denied: return "Denied" 51 | case .disabled: return "Disabled" 52 | case .notDetermined: return "Not Determined" 53 | case .invalid: return "Invalid" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Core/Provider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Provider.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import UIKit 15 | 16 | public class Provider { 17 | 18 | let manager: () -> Permissionable 19 | 20 | /// 是否授权 21 | public var isAuthorized: Bool { 22 | return manager().status == .authorized 23 | } 24 | 25 | /// 授权状态 26 | public var status: PermissionStatus { 27 | return manager().status 28 | } 29 | 30 | public init(_ manager: @escaping @autoclosure () -> Permissionable) { 31 | self.manager = manager 32 | } 33 | 34 | /// 别名 35 | public var alias: (() -> String)? 36 | 37 | /// 请求授权 38 | /// 39 | /// - Parameters: 40 | /// - сompletion: 结果回调 41 | public func request(_ сompletion: @escaping (Bool) -> Void) { 42 | let manager = self.manager() 43 | manager.checkUsageDescriptions() 44 | manager.request { 45 | сompletion(manager.status == .authorized) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | PermissionKit 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Bluetooth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Bluetooth.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2021/11/22. 11 | // Copyright © 2021 lee. All rights reserved. 12 | // 13 | 14 | import Foundation 15 | import CoreBluetooth 16 | 17 | extension Provider { 18 | 19 | public static let bluetooth: Provider = .init(BluetoothManager()) 20 | } 21 | 22 | struct BluetoothManager: Permissionable { 23 | 24 | var status: PermissionStatus { 25 | switch BluetoothState.status { 26 | case .authorized: return .authorized 27 | case .denied: return .denied 28 | case .disabled: return .disabled 29 | case .notDetermined: return .notDetermined 30 | case .invalid: return .invalid 31 | } 32 | } 33 | 34 | var name: String { return "Bluetooth" } 35 | 36 | var usageDescriptions: [String] { 37 | if #available(iOS 13.0, *) { 38 | return ["NSBluetoothAlwaysUsageDescription"] 39 | } 40 | return ["NSBluetoothPeripheralUsageDescription"] 41 | } 42 | 43 | func request(_ сompletion: @escaping () -> Void) { 44 | BluetoothState.request(completion: сompletion) 45 | } 46 | } 47 | 48 | /// 获取蓝牙状态类 49 | public enum BluetoothState { 50 | 51 | /// App 授权状态 52 | public enum Authorization { 53 | case authorized 54 | case denied 55 | case disabled 56 | case notDetermined 57 | case invalid 58 | } 59 | 60 | /// 系统开关状态 61 | public enum Powered { 62 | case notDetermined 63 | case denied 64 | 65 | case on, off 66 | case unknown 67 | } 68 | 69 | public static var status: Authorization { 70 | if #available(iOS 13.1, tvOS 13.1, *) { 71 | switch CBCentralManager.authorization { 72 | case .allowedAlways: return .authorized 73 | case .notDetermined: return .notDetermined 74 | case .restricted: return .denied 75 | case .denied: return .denied 76 | @unknown default: return .denied 77 | } 78 | } else if #available(iOS 13.0, tvOS 13.0, *) { 79 | switch CBCentralManager().authorization { 80 | case .allowedAlways: return .authorized 81 | case .notDetermined: return .notDetermined 82 | case .restricted: return .denied 83 | case .denied: return .denied 84 | @unknown default: return .denied 85 | } 86 | } else { 87 | switch CBPeripheralManager.authorizationStatus() { 88 | case .authorized: return .authorized 89 | case .denied: return .denied 90 | case .restricted: return .denied 91 | case .notDetermined: return .notDetermined 92 | @unknown default: return .denied 93 | } 94 | } 95 | } 96 | 97 | public static func request(completion: @escaping () -> Void) { 98 | BluetoothHandler.shared.completion = completion 99 | BluetoothHandler.shared.requestUpdate() 100 | } 101 | 102 | public static func powered(showPower: Bool = false, _ completion: @escaping (Powered) -> Void) { 103 | switch BluetoothState.status { 104 | case .authorized: 105 | let manager = CBCentralManager(delegate: nil, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey : showPower]) 106 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 107 | switch manager.state { 108 | case .poweredOff: return completion(.off) 109 | case .poweredOn: return completion(.on) 110 | case .unsupported: return completion(.off) 111 | case .unauthorized: return completion(.denied) 112 | default: return completion(.unknown) 113 | } 114 | } 115 | 116 | case .notDetermined: completion(.notDetermined) 117 | case .denied: completion(.denied) 118 | case .disabled: completion(.denied) 119 | case .invalid: completion(.unknown) 120 | } 121 | } 122 | } 123 | 124 | fileprivate class BluetoothHandler: NSObject, CBCentralManagerDelegate { 125 | 126 | var completion: ()->Void = {} 127 | 128 | // MARK: - Init 129 | 130 | static let shared: BluetoothHandler = .init() 131 | 132 | override init() { 133 | super.init() 134 | } 135 | 136 | // MARK: - Manager 137 | 138 | var manager: CBCentralManager? 139 | 140 | func requestUpdate() { 141 | if manager == nil { 142 | self.manager = CBCentralManager(delegate: self, queue: nil, options: [:]) 143 | } else { 144 | completion() 145 | } 146 | } 147 | 148 | func centralManagerDidUpdateState(_ central: CBCentralManager) { 149 | if #available(iOS 13.0, tvOS 13, *) { 150 | switch central.authorization { 151 | case .notDetermined: 152 | break 153 | default: 154 | self.completion() 155 | } 156 | } else { 157 | switch CBPeripheralManager.authorizationStatus() { 158 | case .notDetermined: 159 | break 160 | default: 161 | self.completion() 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Camera.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Camera.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import AVFoundation 15 | 16 | extension Provider { 17 | 18 | public static let camera: Provider = .init(CameraManager()) 19 | 20 | public static let microphone: Provider = .init(MicrophoneManager()) 21 | } 22 | 23 | struct CameraManager: Permissionable { 24 | 25 | var status: PermissionStatus { 26 | switch _status { 27 | case .authorized: return .authorized 28 | case .denied: return .denied 29 | case .restricted: return .disabled 30 | case .notDetermined: return .notDetermined 31 | @unknown default: return .invalid 32 | } 33 | } 34 | 35 | var name: String { return "Camera" } 36 | 37 | var usageDescriptions: [String] { 38 | return ["NSCameraUsageDescription"] 39 | } 40 | 41 | private var _status: AVAuthorizationStatus { 42 | return AVCaptureDevice.authorizationStatus(for: .video) 43 | } 44 | 45 | func request(_ сompletion: @escaping () -> Void) { 46 | guard status == .notDetermined else { 47 | сompletion() 48 | return 49 | } 50 | 51 | AVCaptureDevice.requestAccess(for: .video) { finished in 52 | DispatchQueue.main.async { 53 | сompletion() 54 | } 55 | } 56 | } 57 | } 58 | 59 | struct MicrophoneManager: Permissionable { 60 | 61 | var status: PermissionStatus { 62 | switch _status { 63 | case .granted: return .authorized 64 | case .denied: return .denied 65 | case .undetermined: return .notDetermined 66 | @unknown default: return .invalid 67 | } 68 | } 69 | 70 | var name: String { return "Microphone" } 71 | 72 | var usageDescriptions: [String] { 73 | return ["NSMicrophoneUsageDescription"] 74 | } 75 | 76 | private var _status: AVAudioSession.RecordPermission { 77 | return AVAudioSession.sharedInstance().recordPermission 78 | } 79 | 80 | func request(_ сompletion: @escaping () -> Void) { 81 | guard status == .notDetermined else { 82 | сompletion() 83 | return 84 | } 85 | 86 | AVAudioSession.sharedInstance().requestRecordPermission { finished in 87 | DispatchQueue.main.async { 88 | сompletion() 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Contacts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Contacts.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import Contacts 15 | 16 | extension Provider { 17 | 18 | public static let contacts: Provider = .init(ContactsManager()) 19 | } 20 | 21 | struct ContactsManager: Permissionable { 22 | 23 | var status: PermissionStatus { 24 | switch _status { 25 | case .authorized: return .authorized 26 | case .denied: return .denied 27 | case .restricted: return .disabled 28 | case .notDetermined: return .notDetermined 29 | @unknown default: return .invalid 30 | } 31 | } 32 | 33 | var name: String { return "Contacts" } 34 | 35 | var usageDescriptions: [String] { 36 | return ["NSContactsUsageDescription"] 37 | } 38 | 39 | private var _status: CNAuthorizationStatus { 40 | return CNContactStore.authorizationStatus(for: .contacts) 41 | } 42 | 43 | func request(_ сompletion: @escaping () -> Void) { 44 | guard status == .notDetermined else { 45 | сompletion() 46 | return 47 | } 48 | 49 | let store = CNContactStore() 50 | store.requestAccess(for: .contacts) { (granted, error) in 51 | DispatchQueue.main.async { 52 | сompletion() 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Event.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Event.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import EventKit 15 | 16 | extension Provider { 17 | 18 | public static let calendar: Provider = .init(CalendarManager()) 19 | 20 | public static let reminder: Provider = .init(ReminderManager()) 21 | } 22 | 23 | struct CalendarManager: Permissionable { 24 | 25 | var status: PermissionStatus { 26 | switch _status { 27 | case .authorized: return .authorized 28 | case .denied: return .denied 29 | case .restricted: return .disabled 30 | case .notDetermined: return .notDetermined 31 | case .fullAccess: return .authorized 32 | case .writeOnly: return .authorized 33 | @unknown default: return .invalid 34 | } 35 | } 36 | 37 | var name: String { return "Calendar" } 38 | 39 | var usageDescriptions: [String] { 40 | return ["NSCalendarsUsageDescription"] 41 | } 42 | 43 | private var _status: EKAuthorizationStatus { 44 | return EKEventStore.authorizationStatus(for: .event) 45 | } 46 | 47 | func request(_ сompletion: @escaping () -> Void) { 48 | guard status == .notDetermined else { 49 | сompletion() 50 | return 51 | } 52 | 53 | let store = EKEventStore() 54 | store.requestAccess(to: .event) { (granted: Bool, error: Error?) in 55 | DispatchQueue.main.async { 56 | сompletion() 57 | } 58 | } 59 | } 60 | } 61 | 62 | struct ReminderManager: Permissionable { 63 | 64 | var status: PermissionStatus { 65 | switch _status { 66 | case .authorized: return .authorized 67 | case .denied: return .denied 68 | case .restricted: return .disabled 69 | case .notDetermined: return .notDetermined 70 | case .fullAccess: return .authorized 71 | case .writeOnly: return .authorized 72 | @unknown default: return .invalid 73 | } 74 | } 75 | 76 | var name: String { return "Reminder" } 77 | 78 | var usageDescriptions: [String] { 79 | return ["NSRemindersUsageDescription"] 80 | } 81 | 82 | private var _status: EKAuthorizationStatus { 83 | return EKEventStore.authorizationStatus(for: .reminder) 84 | } 85 | 86 | func request(_ сompletion: @escaping () -> Void) { 87 | guard status == .notDetermined else { 88 | сompletion() 89 | return 90 | } 91 | 92 | let store = EKEventStore() 93 | store.requestAccess(to: .reminder) { (granted: Bool, error: Error?) in 94 | DispatchQueue.main.async { 95 | сompletion() 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Location.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Location.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import CoreLocation 15 | 16 | extension Provider { 17 | 18 | public enum LocationType { 19 | case whenInUse 20 | case alwaysAndWhenInUse 21 | } 22 | 23 | public static func location(_ type: LocationType) -> Provider { 24 | return .init(LocationManager(type)) 25 | } 26 | } 27 | 28 | struct LocationManager: Permissionable { 29 | 30 | private static var delegate: LocationDelegate? 31 | 32 | private let type: Provider.LocationType 33 | 34 | init(_ type: Provider.LocationType) { 35 | self.type = type 36 | } 37 | 38 | var status: PermissionStatus { 39 | guard CLLocationManager.locationServicesEnabled() else { 40 | return .disabled 41 | } 42 | 43 | switch _status { 44 | case .authorizedAlways: return .authorized 45 | case .authorizedWhenInUse: return type == .whenInUse ? .authorized : .denied 46 | case .denied: return .denied 47 | case .restricted: return .disabled 48 | case .notDetermined: return .notDetermined 49 | @unknown default: return .invalid 50 | } 51 | } 52 | 53 | var name: String { return "Location" } 54 | 55 | var usageDescriptions: [String] { 56 | switch type { 57 | case .whenInUse: 58 | return ["NSLocationUsageDescription", 59 | "NSLocationWhenInUseUsageDescription"] 60 | case .alwaysAndWhenInUse: 61 | return ["NSLocationAlwaysUsageDescription", 62 | "NSLocationAlwaysAndWhenInUseUsageDescription"] 63 | } 64 | } 65 | 66 | private var _status: CLAuthorizationStatus { 67 | if #available(iOS 14.0, *) { 68 | return CLLocationManager().authorizationStatus 69 | 70 | } else { 71 | return CLLocationManager.authorizationStatus() 72 | } 73 | } 74 | 75 | func request(_ сompletion: @escaping () -> Void) { 76 | guard status == .notDetermined else { 77 | сompletion() 78 | return 79 | } 80 | let delegate = LocationDelegate(type) { 81 | LocationManager.delegate = nil 82 | сompletion() 83 | } 84 | LocationManager.delegate = delegate 85 | } 86 | } 87 | 88 | class LocationDelegate: NSObject, CLLocationManagerDelegate { 89 | 90 | let manager = CLLocationManager() 91 | let type: Provider.LocationType 92 | let сompletion: () -> Void 93 | 94 | init(_ type: Provider.LocationType, _ сompletion: @escaping () -> Void) { 95 | self.type = type 96 | self.сompletion = сompletion 97 | super.init() 98 | 99 | manager.delegate = self 100 | 101 | switch type { 102 | case .whenInUse: 103 | manager.requestWhenInUseAuthorization() 104 | 105 | case .alwaysAndWhenInUse: 106 | manager.requestAlwaysAuthorization() 107 | } 108 | } 109 | 110 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 111 | guard status != .notDetermined else { 112 | return 113 | } 114 | 115 | self.сompletion() 116 | } 117 | 118 | @available(iOS 14.0, *) 119 | func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { 120 | guard manager.authorizationStatus != .notDetermined else { 121 | return 122 | } 123 | 124 | self.сompletion() 125 | } 126 | 127 | deinit { 128 | manager.delegate = nil 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Media.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Media.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import MediaPlayer 15 | 16 | extension Provider { 17 | 18 | public static let media: Provider = .init(MediaManager()) 19 | } 20 | 21 | struct MediaManager: Permissionable { 22 | 23 | var status: PermissionStatus { 24 | if #available(iOS 9.3, *) { 25 | switch _status { 26 | case .authorized: return .authorized 27 | case .denied: return .denied 28 | case .restricted: return .disabled 29 | case .notDetermined: return .notDetermined 30 | @unknown default: return .invalid 31 | } 32 | } 33 | return .invalid 34 | } 35 | 36 | var name: String { return "Media Library" } 37 | 38 | var usageDescriptions: [String] { 39 | return ["NSAppleMusicUsageDescription"] 40 | } 41 | 42 | @available(iOS 9.3, *) 43 | private var _status: MPMediaLibraryAuthorizationStatus { 44 | return MPMediaLibrary.authorizationStatus() 45 | } 46 | 47 | func request(_ сompletion: @escaping () -> Void) { 48 | guard status == .notDetermined else { 49 | сompletion() 50 | return 51 | } 52 | 53 | if #available(iOS 9.3, *) { 54 | MPMediaLibrary.requestAuthorization() { status in 55 | DispatchQueue.main.async { 56 | сompletion() 57 | } 58 | } 59 | 60 | } else { 61 | сompletion() 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Motion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Motion.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import CoreMotion 15 | 16 | extension Provider { 17 | 18 | public static let motion: Provider = .init(MotionManager()) 19 | } 20 | 21 | struct MotionManager: Permissionable { 22 | 23 | var status: PermissionStatus { 24 | if #available(iOS 11.0, *) { 25 | switch _status { 26 | case .authorized: return .authorized 27 | case .denied: return .denied 28 | case .restricted: return .disabled 29 | case .notDetermined: return .notDetermined 30 | @unknown default: return .invalid 31 | } 32 | } 33 | return .invalid 34 | } 35 | 36 | var name: String { return "Motion" } 37 | 38 | var usageDescriptions: [String] { 39 | return ["NSMotionUsageDescription"] 40 | } 41 | 42 | @available(iOS 11.0, *) 43 | private var _status: CMAuthorizationStatus { 44 | return CMMotionActivityManager.authorizationStatus() 45 | } 46 | 47 | func request(_ сompletion: @escaping () -> Void) { 48 | guard status == .notDetermined else { 49 | сompletion() 50 | return 51 | } 52 | 53 | let manager = CMMotionActivityManager() 54 | let today = Date() 55 | 56 | manager.queryActivityStarting(from: today, to: today, to: .main) { 57 | (activities: [CMMotionActivity]?, error: Error?) in 58 | сompletion() 59 | manager.stopActivityUpdates() 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Notification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Notification.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import UserNotifications 15 | 16 | extension Provider { 17 | 18 | public struct NotificationOptions : OptionSet { 19 | 20 | public typealias RawValue = UInt 21 | 22 | public var rawValue: RawValue 23 | 24 | public init(rawValue: RawValue) { 25 | self.rawValue = rawValue 26 | } 27 | 28 | public static var badge: NotificationOptions { .init(rawValue: 1) } 29 | 30 | public static var sound: NotificationOptions { .init(rawValue: 2) } 31 | 32 | public static var alert: NotificationOptions { .init(rawValue: 4) } 33 | 34 | public static var carPlay: NotificationOptions { .init(rawValue: 8) } 35 | 36 | //available(iOS 12.0, *) 37 | public static var criticalAlert: NotificationOptions { .init(rawValue: 16) } 38 | 39 | //available(iOS 12.0, *) 40 | public static var providesAppNotificationSettings: NotificationOptions { .init(rawValue: 32) } 41 | 42 | //available(iOS 12.0, *) 43 | public static var provisional: NotificationOptions { .init(rawValue: 64) } 44 | 45 | //available(iOS 13.0, *) 46 | public static var announcement: NotificationOptions { .init(rawValue: 128) } 47 | } 48 | 49 | public static func notification(_ options: NotificationOptions) -> Provider { 50 | return .init(NotificationManager(options)) 51 | } 52 | } 53 | 54 | struct NotificationManager: Permissionable { 55 | 56 | @available(iOS 10.0, *) 57 | private static var status: UNAuthorizationStatus? 58 | @available(iOS 10.0, *) 59 | private static let observer: Void = { 60 | NotificationCenter.default.addObserver( 61 | forName: UIApplication.willEnterForegroundNotification, 62 | object: nil, 63 | queue: .main, 64 | using: { _ in 65 | status = nil 66 | UNUserNotificationCenter.current().getNotificationSettings { value in 67 | DispatchQueue.main.async { 68 | status = value.authorizationStatus 69 | } 70 | } 71 | } 72 | ) 73 | } () 74 | 75 | private let options: Provider.NotificationOptions 76 | 77 | init(_ options: Provider.NotificationOptions) { 78 | self.options = options 79 | 80 | if #available(iOS 10.0, *) { 81 | NotificationManager.observer 82 | } 83 | } 84 | 85 | var status: PermissionStatus { 86 | if #available(iOS 10.0, *) { 87 | switch _status { 88 | case .authorized, .provisional, .ephemeral: return .authorized 89 | case .denied: return .denied 90 | case .notDetermined: return .notDetermined 91 | @unknown default: return .invalid 92 | } 93 | 94 | } else { 95 | let settings = UIApplication.shared.currentUserNotificationSettings 96 | let types = settings?.types ?? [] 97 | let isRequested = UserDefaults.standard.isRequestedNotifications 98 | return types.isEmpty ? (isRequested ? .denied : .notDetermined) : .authorized 99 | } 100 | } 101 | 102 | var name: String { return "Notification" } 103 | 104 | var usageDescriptions: [String] { 105 | return [] 106 | } 107 | 108 | @available(iOS 10.0, *) 109 | private var _status: UNAuthorizationStatus { 110 | guard let status = NotificationManager.status else { 111 | var settings: UNNotificationSettings? 112 | let semaphore = DispatchSemaphore(value: 0) 113 | DispatchQueue.global().async { 114 | UNUserNotificationCenter.current().getNotificationSettings { value in 115 | settings = value 116 | semaphore.signal() 117 | } 118 | } 119 | semaphore.wait() 120 | NotificationManager.status = settings?.authorizationStatus 121 | return NotificationManager.status ?? .denied 122 | } 123 | return status 124 | } 125 | 126 | func request(_ сompletion: @escaping () -> Void) { 127 | defer { 128 | UIApplication.shared.registerForRemoteNotifications() 129 | } 130 | guard status == .notDetermined else { 131 | сompletion() 132 | return 133 | } 134 | 135 | if #available(iOS 10.0, *) { 136 | let center = UNUserNotificationCenter.current() 137 | center.requestAuthorization(options: options.options) { 138 | (granted, error) in 139 | center.getNotificationSettings { value in 140 | DispatchQueue.main.async { 141 | NotificationManager.status = value.authorizationStatus 142 | сompletion() 143 | } 144 | } 145 | } 146 | 147 | } else { 148 | var object: Any? 149 | object = NotificationCenter.default.addObserver( 150 | forName: UIApplication.didBecomeActiveNotification, 151 | object: nil, 152 | queue: .main, 153 | using: { _ in 154 | NotificationCenter.default.removeObserver(object ?? 0) 155 | UserDefaults.standard.isRequestedNotifications = true 156 | DispatchQueue.main.async { 157 | сompletion() 158 | } 159 | } 160 | ) 161 | 162 | let settings = UIUserNotificationSettings( 163 | types: options.types, 164 | categories: nil 165 | ) 166 | UIApplication.shared.registerUserNotificationSettings(settings) 167 | } 168 | } 169 | } 170 | 171 | fileprivate extension UserDefaults { 172 | 173 | var isRequestedNotifications: Bool { 174 | get { return bool(forKey: "Permission.RequestedNotifications") } 175 | set { set(newValue, forKey: "Permission.RequestedNotifications") } 176 | } 177 | } 178 | 179 | private extension Provider.NotificationOptions { 180 | 181 | @available(iOS 10.0, *) 182 | var options: UNAuthorizationOptions { 183 | .init(rawValue: rawValue) 184 | } 185 | 186 | var types: UIUserNotificationType { 187 | .init(rawValue: rawValue) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Photos.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Photos.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import Photos 15 | 16 | extension Provider { 17 | 18 | public enum PhotosType { 19 | case addOnly 20 | case readWrite 21 | } 22 | 23 | public static func photos(_ type: PhotosType) -> Provider { 24 | return .init(PhotosManager(type)) 25 | } 26 | } 27 | 28 | struct PhotosManager: Permissionable { 29 | 30 | private let type: Provider.PhotosType 31 | 32 | init(_ type: Provider.PhotosType) { 33 | self.type = type 34 | } 35 | 36 | var status: PermissionStatus { 37 | switch _status { 38 | case .authorized: return .authorized 39 | case .limited: return .authorized 40 | case .denied: return .denied 41 | case .restricted: return .disabled 42 | case .notDetermined: return .notDetermined 43 | @unknown default: return .invalid 44 | } 45 | } 46 | 47 | var name: String { return "Photo Library" } 48 | 49 | var usageDescriptions: [String] { 50 | return ["NSPhotoLibraryUsageDescription", 51 | "NSPhotoLibraryAddUsageDescription"] 52 | } 53 | 54 | private var _status: PHAuthorizationStatus { 55 | if #available(iOS 14, *) { 56 | return PHPhotoLibrary.authorizationStatus(for: type.level) 57 | 58 | } else { 59 | return PHPhotoLibrary.authorizationStatus() 60 | } 61 | } 62 | 63 | func request(_ сompletion: @escaping () -> Void) { 64 | guard status == .notDetermined else { 65 | сompletion() 66 | return 67 | } 68 | 69 | if #available(iOS 14, *) { 70 | PHPhotoLibrary.requestAuthorization(for: type.level) { status in 71 | DispatchQueue.main.async { 72 | сompletion() 73 | } 74 | } 75 | 76 | } else { 77 | PHPhotoLibrary.requestAuthorization { status in 78 | DispatchQueue.main.async { 79 | сompletion() 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | private extension Provider.PhotosType { 87 | 88 | @available(iOS 14, *) 89 | var level: PHAccessLevel { 90 | switch self { 91 | case .addOnly: 92 | return .addOnly 93 | 94 | case .readWrite: 95 | return .readWrite 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Siri.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Siri.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import Intents 15 | 16 | extension Provider { 17 | 18 | public static let siri: Provider = .init(SiriManager()) 19 | } 20 | 21 | struct SiriManager: Permissionable { 22 | 23 | var status: PermissionStatus { 24 | if #available(iOS 10.0, *) { 25 | switch _status { 26 | case .authorized: return .authorized 27 | case .denied: return .denied 28 | case .restricted: return .disabled 29 | case .notDetermined: return .notDetermined 30 | @unknown default: return .invalid 31 | } 32 | } 33 | return .denied 34 | } 35 | 36 | var name: String { return "Siri" } 37 | 38 | var usageDescriptions: [String] { 39 | return ["NSSiriUsageDescription"] 40 | } 41 | 42 | @available(iOS 10.0, *) 43 | private var _status: INSiriAuthorizationStatus { 44 | return INPreferences.siriAuthorizationStatus() 45 | } 46 | 47 | func request(_ сompletion: @escaping () -> Void) { 48 | guard status == .notDetermined else { 49 | сompletion() 50 | return 51 | } 52 | 53 | if #available(iOS 10.0, *) { 54 | INPreferences.requestSiriAuthorization { (status) in 55 | DispatchQueue.main.async { 56 | сompletion() 57 | } 58 | } 59 | 60 | } else { 61 | сompletion() 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Speech.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Speech.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2019/6/3. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import Speech 15 | 16 | extension Provider { 17 | 18 | public static let speech: Provider = .init(SpeechManager()) 19 | } 20 | 21 | struct SpeechManager: Permissionable { 22 | 23 | var status: PermissionStatus { 24 | if #available(iOS 10.0, *) { 25 | switch _status { 26 | case .authorized: return .authorized 27 | case .denied: return .denied 28 | case .restricted: return .disabled 29 | case .notDetermined: return .notDetermined 30 | @unknown default: return .invalid 31 | } 32 | } 33 | return .invalid 34 | } 35 | 36 | var name: String { return "Speech" } 37 | 38 | var usageDescriptions: [String] { 39 | return ["NSMicrophoneUsageDescription", 40 | "NSSpeechRecognitionUsageDescription"] 41 | } 42 | 43 | @available(iOS 10.0, *) 44 | private var _status: SFSpeechRecognizerAuthorizationStatus { 45 | return SFSpeechRecognizer.authorizationStatus() 46 | } 47 | 48 | func request(_ сompletion: @escaping () -> Void) { 49 | guard status == .notDetermined else { 50 | сompletion() 51 | return 52 | } 53 | 54 | if #available(iOS 10.0, *) { 55 | SFSpeechRecognizer.requestAuthorization { status in 56 | DispatchQueue.main.async { 57 | сompletion() 58 | } 59 | } 60 | 61 | } else { 62 | сompletion() 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Managers/Permission.Tracking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Permission.Tracking.swift 3 | // ┌─┐ ┌───────┐ ┌───────┐ 4 | // │ │ │ ┌─────┘ │ ┌─────┘ 5 | // │ │ │ └─────┐ │ └─────┐ 6 | // │ │ │ ┌─────┘ │ ┌─────┘ 7 | // │ └─────┐│ └─────┐ │ └─────┐ 8 | // └───────┘└───────┘ └───────┘ 9 | // 10 | // Created by lee on 2021/5/6. 11 | // Copyright © 2019年 lee. All rights reserved. 12 | // 13 | 14 | import AppTrackingTransparency 15 | import AdSupport 16 | 17 | extension Provider { 18 | 19 | public static let tracking: Provider = .init(TrackingManager()) 20 | } 21 | 22 | struct TrackingManager: Permissionable { 23 | 24 | var status: PermissionStatus { 25 | if #available(iOS 14, *) { 26 | switch _status { 27 | case .authorized: return .authorized 28 | case .denied: return .denied 29 | case .restricted: return .disabled 30 | case .notDetermined: return .notDetermined 31 | @unknown default: return .invalid 32 | } 33 | 34 | } else { 35 | return ASIdentifierManager.shared().isAdvertisingTrackingEnabled ? .authorized : .denied 36 | } 37 | } 38 | 39 | var name: String { return "Tracking" } 40 | 41 | var usageDescriptions: [String] { 42 | return ["NSUserTrackingUsageDescription"] 43 | } 44 | 45 | @available(iOS 14, *) 46 | private var _status: ATTrackingManager.AuthorizationStatus { 47 | return ATTrackingManager.trackingAuthorizationStatus 48 | } 49 | 50 | func request(_ сompletion: @escaping () -> Void) { 51 | guard status == .notDetermined else { 52 | сompletion() 53 | return 54 | } 55 | 56 | if #available(iOS 14, *) { 57 | ATTrackingManager.requestTrackingAuthorization { _ in 58 | DispatchQueue.main.async { 59 | сompletion() 60 | } 61 | } 62 | 63 | } else { 64 | сompletion() 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/PermissionKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionKit.h 3 | // PermissionKit 4 | // 5 | // Created by 李响 on 2019/6/3. 6 | // Copyright © 2019 swift. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for PermissionKit. 12 | FOUNDATION_EXPORT double PermissionKitVersionNumber; 13 | 14 | //! Project version string for PermissionKit. 15 | FOUNDATION_EXPORT const unsigned char PermissionKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTracking 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | NSPrivacyTrackingDomains 10 | 11 | NSPrivacyCollectedDataTypes 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------