From 8f9241455efcac6d47374db5ced66657c2684631 Mon Sep 17 00:00:00 2001 From: Alan Scarpa Date: Thu, 17 Nov 2016 11:43:03 -0500 Subject: [PATCH] Organize permissions --- PermissionScope.podspec | 87 ++++++++++++ PermissionScope.xcodeproj/project.pbxproj | 4 + PermissionScope/PermissionScope.swift | 166 +++++++++++++++------- PermissionScope/Permissions.swift | 29 ++-- PermissionScope/Structs.swift | 57 +++++++- 5 files changed, 279 insertions(+), 64 deletions(-) diff --git a/PermissionScope.podspec b/PermissionScope.podspec index 8bebad0..37e62f3 100644 --- a/PermissionScope.podspec +++ b/PermissionScope.podspec @@ -13,4 +13,91 @@ Pod::Spec.new do |s| s.source_files = 'PermissionScope/*.swift' s.requires_arc = true + + s.default_subspec = 'Core' + + s.subspec 'Core' do |core| + core.source_files = 'PermissionScope/*.{swift,h}' + end + + s.subspec 'Motion' do |motion| + motion.dependency 'PermissionScope/Core' + motion.weak_framework = 'CoreMotion' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestMotionEnabled' } + motion.pod_target_xcconfig = feature_flags + motion.user_target_xcconfig = feature_flags + end + + s.subspec 'Bluetooth' do |bluetooth| + bluetooth.dependency 'PermissionScope/Core' + bluetooth.weak_framework = 'CoreBluetooth' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestBluetoothEnabled' } + bluetooth.pod_target_xcconfig = feature_flags + bluetooth.user_target_xcconfig = feature_flags + end + + s.subspec 'Location' do |location| + location.dependency 'PermissionScope/Core' + location.weak_framework = 'CoreLocation' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestLocationEnabled' } + location.pod_target_xcconfig = feature_flags + location.user_target_xcconfig = feature_flags + end + + s.subspec 'Microphone' do |mic| + mic.dependency 'PermissionScope/Core' + mic.weak_framework = 'AVFoundation' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestMicrophoneEnabled' } + mic.pod_target_xcconfig = feature_flags + mic.user_target_xcconfig = feature_flags + end + + s.subspec 'PhotoLibrary' do |photo| + photo.dependency 'PermissionScope/Core' + photo.weak_framework = 'Photos', 'AssetsLibrary' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestPhotoLibraryEnabled' } + photo.pod_target_xcconfig = feature_flags + photo.user_target_xcconfig = feature_flags + end + + s.subspec 'Camera' do |cam| + cam.dependency 'PermissionScope/Core' + cam.weak_framework = 'AVFoundation' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestCameraEnabled' } + cam.pod_target_xcconfig = feature_flags + cam.user_target_xcconfig = feature_flags + end + + s.subspec 'Notifications' do |note| + note.dependency 'PermissionScope/Core' + note.weak_framework = 'UIKit', 'UserNotifications' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestNotificationsEnabled' } + note.pod_target_xcconfig = feature_flags + note.user_target_xcconfig = feature_flags + end + + s.subspec 'Contacts' do |contacts| + contacts.dependency 'PermissionScope/Core' + contacts.weak_framework = 'Contacts', 'AddressBook' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestContactsEnabled' } + contacts.pod_target_xcconfig = feature_flags + contacts.user_target_xcconfig = feature_flags + end + + s.subspec 'Events' do |cal| + cal.dependency 'PermissionScope/Core' + cal.weak_framework = 'EventKit' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestEventsEnabled' } + cal.pod_target_xcconfig = feature_flags + cal.user_target_xcconfig = feature_flags + end + + s.subspec 'Reminders' do |rem| + rem.dependency 'PermissionScope/Core' + rem.weak_framework = 'EventKit' + feature_flags = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'PermissionScopeRequestRemindersEnabled' } + rem.pod_target_xcconfig = feature_flags + rem.user_target_xcconfig = feature_flags + end + end diff --git a/PermissionScope.xcodeproj/project.pbxproj b/PermissionScope.xcodeproj/project.pbxproj index 097171a..fbc4b39 100644 --- a/PermissionScope.xcodeproj/project.pbxproj +++ b/PermissionScope.xcodeproj/project.pbxproj @@ -592,9 +592,11 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "com.thatthinginswift.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = PermissionScope; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "PermissionScopeRequestMotionEnabled PermissionScopeRequestBluetoothEnabled PermissionScopeRequestLocationEnabled PermissionScopeRequestMicrophoneEnabled PermissionScopeRequestPhotoLibraryEnabled PermissionScopeRequestCameraEnabled PermissionScopeRequestNotificationsEnabled PermissionScopeRequestContactsEnabled PermissionScopeRequestEventsEnabled PermissionScopeRequestRemindersEnabled"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; }; @@ -613,9 +615,11 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "com.thatthinginswift.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = PermissionScope; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "PermissionScopeRequestMotionEnabled PermissionScopeRequestBluetoothEnabled PermissionScopeRequestLocationEnabled PermissionScopeRequestMicrophoneEnabled PermissionScopeRequestPhotoLibraryEnabled PermissionScopeRequestCameraEnabled PermissionScopeRequestNotificationsEnabled PermissionScopeRequestContactsEnabled PermissionScopeRequestEventsEnabled PermissionScopeRequestRemindersEnabled"; SWIFT_VERSION = 3.0; }; name = Release; diff --git a/PermissionScope/PermissionScope.swift b/PermissionScope/PermissionScope.swift index fe7023c..03c5dd5 100644 --- a/PermissionScope/PermissionScope.swift +++ b/PermissionScope/PermissionScope.swift @@ -6,22 +6,46 @@ // Copyright (c) 2015 That Thing in Swift. All rights reserved. // +import Foundation import UIKit + +#if PermissionScopeRequestMotionEnabled +import CoreMotion +#endif + +#if PermissionScopeRequestBluetoothEnabled +import CoreBluetooth +extension PermissionScope: CBPeripheralManagerDelegate {} +#endif + +#if PermissionScopeRequestLocationEnabled import CoreLocation -import AddressBook +extension PermissionScope: CLLocationManagerDelegate {} +#endif + +#if PermissionScopeRequestMicrophoneEnabled || PermissionScopeRequestCameraEnabled import AVFoundation +#endif + +#if PermissionScopeRequestPhotoLibraryEnabled import Photos -import EventKit -import CoreBluetooth -import CoreMotion +#endif + +#if PermissionScopeRequestContactsEnabled +import AddressBook import Contacts +#endif + +#if PermissionScopeRequestEventsEnabled || PermissionScopeRequestRemindersEnabled +import EventKit +#endif public typealias statusRequestClosure = (_ status: PermissionStatus) -> Void public typealias authClosureType = (_ finished: Bool, _ results: [PermissionResult]) -> Void public typealias cancelClosureType = (_ results: [PermissionResult]) -> Void typealias resultsForConfigClosure = ([PermissionResult]) -> Void -@objc public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGestureRecognizerDelegate, CBPeripheralManagerDelegate { +@objc public class PermissionScope: UIViewController, UIGestureRecognizerDelegate { // MARK: UI Parameters @@ -61,19 +85,6 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void public let contentView = UIView() // MARK: - Various lazy managers - lazy var locationManager:CLLocationManager = { - let lm = CLLocationManager() - lm.delegate = self - return lm - }() - - lazy var bluetoothManager:CBPeripheralManager = { - return CBPeripheralManager(delegate: self, queue: nil, options:[CBPeripheralManagerOptionShowPowerAlertKey: false]) - }() - - lazy var motionManager:CMMotionActivityManager = { - return CMMotionActivityManager() - }() /// NSUserDefaults standardDefaults lazy var lazy var defaults:UserDefaults = { @@ -388,12 +399,21 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void // MARK: - Status and Requests for each permission // MARK: Location - + + #if PermissionScopeRequestLocationEnabled + + lazy var locationManager:CLLocationManager = { + let lm = CLLocationManager() + lm.delegate = self + return lm + }() + /** - Returns the current permission status for accessing LocationAlways. - - - returns: Permission status for the requested type. - */ + Returns the current permission status for accessing LocationAlways. + + - returns: Permission status for the requested type. + */ + public func statusLocationAlways() -> PermissionStatus { guard CLLocationManager.locationServicesEnabled() else { return .disabled } @@ -483,8 +503,17 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void } } + // MARK: Location delegate + + public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + detectAndCallback() + } + #endif + // MARK: Contacts - + + #if PermissionScopeRequestContactsEnabled + /** Returns the current permission status for accessing Contacts. @@ -538,6 +567,7 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void break } } + #endif // MARK: Notifications @@ -546,6 +576,7 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void - returns: Permission status for the requested type. */ + #if PermissionScopeRequestNotificationsEnabled public func statusNotifications() -> PermissionStatus { let settings = UIApplication.shared.currentUserNotificationSettings if let settingTypes = settings?.types , settingTypes != UIUserNotificationType() { @@ -652,9 +683,12 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void detectAndCallback() } } - + #endif + // MARK: Microphone - + + #if PermissionScopeRequestMicrophoneEnabled + /** Returns the current permission status for accessing the Microphone. @@ -690,9 +724,12 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void break } } + #endif // MARK: Camera - + + #if PermissionScopeRequestCameraEnabled + /** Returns the current permission status for accessing the Camera. @@ -729,9 +766,12 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void break } } + #endif // MARK: Photos - + + #if PermissionScopeRequestPhotoLibraryEnabled + /** Returns the current permission status for accessing Photos. @@ -767,9 +807,12 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void break } } + #endif // MARK: Reminders - + + #if PermissionScopeRequestRemindersEnabled + /** Returns the current permission status for accessing Reminders. @@ -804,9 +847,12 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void break } } + #endif // MARK: Events - + + #if PermissionScopeRequestEventsEnabled + /** Returns the current permission status for accessing Events. @@ -841,9 +887,16 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void break } } + #endif // MARK: Bluetooth - + + #if PermissionScopeRequestBluetoothEnabled + + lazy var bluetoothManager:CBPeripheralManager = { + return CBPeripheralManager(delegate: self, queue: nil, options:[CBPeripheralManagerOptionShowPowerAlertKey: false]) + }() + /// Returns whether Bluetooth access was asked before or not. fileprivate var askedBluetooth:Bool { get { @@ -855,9 +908,6 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void } } - /// Returns whether PermissionScope is waiting for the user to enable/disable bluetooth access or not. - fileprivate var waitingForBluetooth = false - /** Returns the current permission status for accessing Bluetooth. @@ -902,7 +952,7 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void } } - + /** Start and immediately stop bluetooth advertising to trigger its permission dialog. @@ -915,9 +965,28 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void waitingForBluetooth = true } } - + + // MARK: Bluetooth delegate + + public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { + waitingForBluetooth = false + detectAndCallback() + } + + #endif + + /// Returns whether PermissionScope is waiting for the user to enable/disable bluetooth access or not. + /// This is outside of #endif because it is accessed by another method that is not dependant on Bluetooth being enabled. + fileprivate var waitingForBluetooth = false + // MARK: Core Motion Activity - + + #if PermissionScopeRequestMotionEnabled + + lazy var motionManager:CMMotionActivityManager = { + return CMMotionActivityManager() + }() + /** Returns the current permission status for accessing Core Motion Activity. @@ -984,8 +1053,11 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void defaults.synchronize() } } - + + #endif + /// Returns whether PermissionScope is waiting for the user to enable/disable motion access or not. + /// This is outside of #endif because it is accessed by another method that is not dependant on Motion being enabled. fileprivate var waitingForMotion = false // MARK: - UI @@ -1003,7 +1075,8 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void onCancel = cancelled DispatchQueue.main.async { - while self.waitingForBluetooth || self.waitingForMotion { } + while + self.waitingForBluetooth || self.waitingForMotion { } // call other methods that need to wait before show // no missing required perms? callback and do nothing self.requiredAuthorized({ areAuthorized in @@ -1018,7 +1091,7 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void }) } } - + /** Creates the modal viewcontroller and shows it. */ @@ -1101,19 +1174,6 @@ typealias resultsForConfigClosure = ([PermissionResult]) -> Void return false } - // MARK: Location delegate - - public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - detectAndCallback() - } - - // MARK: Bluetooth delegate - - public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { - waitingForBluetooth = false - detectAndCallback() - } - // MARK: - UI Helpers /** diff --git a/PermissionScope/Permissions.swift b/PermissionScope/Permissions.swift index a5cf0e7..88065c9 100644 --- a/PermissionScope/Permissions.swift +++ b/PermissionScope/Permissions.swift @@ -7,15 +7,6 @@ // import Foundation -import CoreLocation -import AddressBook -import AVFoundation -import Photos -import EventKit -import CoreBluetooth -import CoreMotion -import CloudKit -import Accounts /** * Protocol for permission configurations. @@ -25,6 +16,7 @@ import Accounts var type: PermissionType { get } } +#if PermissionScopeRequestNotificationsEnabled @objc public class NotificationsPermission: NSObject, Permission { public let type: PermissionType = .notifications public let notificationCategories: Set? @@ -33,7 +25,9 @@ import Accounts self.notificationCategories = notificationCategories } } +#endif +#if PermissionScopeRequestLocationEnabled @objc public class LocationWhileInUsePermission: NSObject, Permission { public let type: PermissionType = .locationInUse } @@ -41,38 +35,55 @@ import Accounts @objc public class LocationAlwaysPermission: NSObject, Permission { public let type: PermissionType = .locationAlways } +#endif +#if PermissionScopeRequestContactsEnabled @objc public class ContactsPermission: NSObject, Permission { public let type: PermissionType = .contacts } +#endif public typealias requestPermissionUnknownResult = () -> Void public typealias requestPermissionShowAlert = (PermissionType) -> Void +#if PermissionScopeRequestEventsEnabled @objc public class EventsPermission: NSObject, Permission { public let type: PermissionType = .events } +#endif +#if PermissionScopeRequestMicrophoneEnabled @objc public class MicrophonePermission: NSObject, Permission { public let type: PermissionType = .microphone } +#endif +#if PermissionScopeRequestCameraEnabled @objc public class CameraPermission: NSObject, Permission { public let type: PermissionType = .camera } +#endif +#if PermissionScopeRequestPhotoLibraryEnabled @objc public class PhotosPermission: NSObject, Permission { public let type: PermissionType = .photos } +#endif +#if PermissionScopeRequestRemindersEnabled @objc public class RemindersPermission: NSObject, Permission { public let type: PermissionType = .reminders } +#endif +#if PermissionScopeRequestBluetoothEnabled @objc public class BluetoothPermission: NSObject, Permission { public let type: PermissionType = .bluetooth } +#endif +#if PermissionScopeRequestMotionEnabled @objc public class MotionPermission: NSObject, Permission { public let type: PermissionType = .motion } +#endif diff --git a/PermissionScope/Structs.swift b/PermissionScope/Structs.swift index 09667e1..19e6df3 100644 --- a/PermissionScope/Structs.swift +++ b/PermissionScope/Structs.swift @@ -10,9 +10,40 @@ import Foundation /// Permissions currently supportes by PermissionScope @objc public enum PermissionType: Int, CustomStringConvertible { - case contacts, locationAlways, locationInUse, notifications, microphone, camera, photos, reminders, events, bluetooth, motion + #if PermissionScopeRequestContactsEnabled + case contacts + #endif + #if PermissionScopeRequestLocationEnabled + case locationAlways + case locationInUse + #endif + #if PermissionScopeRequestNotificationsEnabled + case notifications + #endif + #if PermissionScopeRequestMicrophoneEnabled + case microphone + #endif + #if PermissionScopeRequestCameraEnabled + case camera + #endif + #if PermissionScopeRequestPhotoLibraryEnabled + case photos + #endif + #if PermissionScopeRequestRemindersEnabled + case reminders + #endif + #if PermissionScopeRequestEventsEnabled + case events + #endif + #if PermissionScopeRequestBluetoothEnabled + case bluetooth + #endif + #if PermissionScopeRequestMotionEnabled + case motion + #endif public var prettyDescription: String { + // TODO: This will not compile due to same problem described below. switch self { case .locationAlways, .locationInUse: return "Location" @@ -22,6 +53,8 @@ import Foundation } public var description: String { + /* TODO: This will not compile when used in a project (unless project is asking ALL permissions) because any permission that is not used will have it's enum undefined due to the #ifendif statements around the enum definition above. + */ switch self { case .contacts: return "Contacts" case .events: return "Events" @@ -37,7 +70,14 @@ import Foundation } } - static let allValues = [contacts, locationAlways, locationInUse, notifications, microphone, camera, photos, reminders, events, bluetooth, motion] + static var allValues: [PermissionType] { + var values = [PermissionType]() + for permission in iterateEnum(PermissionType.self) { + guard let permissionType = PermissionType(rawValue: permission.rawValue) else { continue } + values.append(permissionType) + } + return values + } } /// Possible statuses for a permission. @@ -68,3 +108,16 @@ import Foundation return "\(type) \(status)" } } + +/* Used to iterate through available PermissionType based on permissions in use. Taken from http://stackoverflow.com/a/28341290/3880396 */ +func iterateEnum(_: T.Type) -> AnyIterator { + var i = 0 + return AnyIterator { + let next = withUnsafePointer(to: &i) { + $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } + } + if next.hashValue != i { return nil } + i += 1 + return next + } +}