├── .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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------