Last active
October 5, 2021 12:28
-
-
Save tadija/c20479d0012baaaacd699ecdc1511579 to your computer and use it in GitHub Desktop.
AEPermissions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* https://gist.github.com/tadija/c20479d0012baaaacd699ecdc1511579 | |
* Revision 8 | |
* Copyright © 2018-2021 Marko Tadić | |
* Licensed under the MIT license | |
*/ | |
import Foundation | |
// MARK: - Core | |
public enum PermissionStatus: String { | |
case authorized | |
case denied | |
case notDetermined | |
public var isAuthorized: Bool { | |
self == .authorized | |
} | |
} | |
public protocol PermissionItem { | |
var status: PermissionStatus { get } | |
func request(then completion: @escaping (PermissionStatus) -> Void) | |
func refresh(then completion: ((PermissionStatus) -> Void)?) | |
} | |
public extension PermissionItem { | |
func refresh(then completion: ((PermissionStatus) -> Void)?) { | |
completion?(status) | |
} | |
} | |
// MARK: - Facade | |
import UIKit | |
@available(iOS 11.0, *) | |
open class Permission: PermissionItem { | |
public enum ItemType: CaseIterable { | |
case microphone | |
case camera | |
case photoLibrary | |
case notifications | |
case locationWhenInUse | |
case locationAlways | |
} | |
private let item: PermissionItem | |
public init(_ itemType: ItemType) { | |
switch itemType { | |
case .microphone: | |
item = Microphone() | |
case .camera: | |
item = Camera() | |
case .photoLibrary: | |
item = PhotoLibrary() | |
case .notifications: | |
item = Notifications() | |
case .locationWhenInUse: | |
item = LocationWhenInUse() | |
case .locationAlways: | |
item = LocationAlways() | |
} | |
} | |
public var status: PermissionStatus { | |
item.status | |
} | |
public func request(then completion: @escaping (PermissionStatus) -> Void) { | |
item.request { (status) in | |
DispatchQueue.main.async { | |
completion(status) | |
} | |
} | |
} | |
public func refresh(then completion: ((PermissionStatus) -> Void)?) { | |
item.refresh { (status) in | |
DispatchQueue.main.async { | |
completion?(status) | |
} | |
} | |
} | |
} | |
extension PermissionItem { | |
func containsUsageDescription(forKey key: String) -> Bool { | |
guard Bundle.main.object(forInfoDictionaryKey: key) != nil else { | |
assertionFailure("Permission Error: \(key) not found in Info.plist") | |
return false | |
} | |
return true | |
} | |
} | |
// MARK: - Microphone | |
import AVFoundation | |
private struct Microphone: PermissionItem { | |
var status: PermissionStatus { | |
switch AVAudioSession.sharedInstance().recordPermission { | |
case .granted: | |
return .authorized | |
case .denied: | |
return .denied | |
case .undetermined: | |
return .notDetermined | |
@unknown default: | |
return .notDetermined | |
} | |
} | |
func request(then completion: @escaping (PermissionStatus) -> Void) { | |
precondition(containsUsageDescription(forKey: "NSMicrophoneUsageDescription")) | |
AVAudioSession.sharedInstance().requestRecordPermission { _ in | |
completion(self.status) | |
} | |
} | |
} | |
// MARK: - Camera | |
private struct Camera: PermissionItem { | |
var status: PermissionStatus { | |
let systemStatus = AVCaptureDevice.authorizationStatus(for: .video) | |
switch systemStatus { | |
case .authorized: | |
return .authorized | |
case .restricted, .denied: | |
return .denied | |
case .notDetermined: | |
return .notDetermined | |
@unknown default: | |
return .notDetermined | |
} | |
} | |
func request(then completion: @escaping (PermissionStatus) -> Void) { | |
precondition(containsUsageDescription(forKey: "NSCameraUsageDescription")) | |
AVCaptureDevice.requestAccess(for: .video) { _ in | |
completion(self.status) | |
} | |
} | |
} | |
// MARK: - PhotoLibrary | |
import Photos | |
private struct PhotoLibrary: PermissionItem { | |
var status: PermissionStatus { | |
let systemStatus = PHPhotoLibrary.authorizationStatus() | |
switch systemStatus { | |
case .authorized, .limited: | |
return .authorized | |
case .restricted, .denied: | |
return .denied | |
case .notDetermined: | |
return .notDetermined | |
@unknown default: | |
return .notDetermined | |
} | |
} | |
func request(then completion: @escaping (PermissionStatus) -> Void) { | |
precondition(containsUsageDescription(forKey: "NSPhotoLibraryUsageDescription")) | |
PHPhotoLibrary.requestAuthorization { _ in | |
completion(self.status) | |
} | |
} | |
} | |
// MARK: - Notifications | |
import UserNotifications | |
@available(iOS 11.0, *) | |
private class Notifications: PermissionItem { | |
private let center = UNUserNotificationCenter.current() | |
var status: PermissionStatus { | |
let status = settings?.authorizationStatus ?? .notDetermined | |
switch status { | |
case .authorized, .provisional, .ephemeral: | |
return .authorized | |
case .denied: | |
return .denied | |
case .notDetermined: | |
return .notDetermined | |
@unknown default: | |
return .notDetermined | |
} | |
} | |
func request(then completion: @escaping (PermissionStatus) -> Void) { | |
center.requestAuthorization(options: [.sound, .alert, .badge]) { [unowned self] _, _ in | |
self.refresh(then: completion) | |
} | |
} | |
func refresh(then completion: ((PermissionStatus) -> Void)? = nil) { | |
center.getNotificationSettings { [unowned self] settings in | |
self.settings = settings | |
completion?(self.status) | |
} | |
} | |
init() { | |
refresh() | |
} | |
private let settingsKey = "AEPermissions.Notifications.Settings" | |
private var settings: UNNotificationSettings? { | |
get { | |
guard | |
let data = UserDefaults.standard.object(forKey: settingsKey) as? NSData, | |
let settings = try? NSKeyedUnarchiver.unarchivedObject( | |
ofClass: UNNotificationSettings.self, from: data as Data | |
) | |
else { | |
return nil | |
} | |
return settings | |
} | |
set { | |
if let settings = newValue { | |
let data = try? NSKeyedArchiver.archivedData( | |
withRootObject: settings, requiringSecureCoding: true | |
) | |
UserDefaults.standard.set(data, forKey: settingsKey) | |
UserDefaults.standard.synchronize() | |
} | |
} | |
} | |
} | |
// MARK: - Location | |
import CoreLocation | |
private class Location: NSObject, PermissionItem, CLLocationManagerDelegate { | |
enum AccessLevel { | |
case whenInUse | |
case always | |
} | |
private let locationManager = CLLocationManager() | |
private var systemStatus: CLAuthorizationStatus { | |
if #available(iOS 14, *) { | |
return locationManager.authorizationStatus | |
} else { | |
return CLLocationManager.authorizationStatus() | |
} | |
} | |
var status: PermissionStatus { | |
switch systemStatus { | |
case .authorizedWhenInUse, .authorizedAlways: | |
return .authorized | |
case .restricted, .denied: | |
return .denied | |
case .notDetermined: | |
return .notDetermined | |
@unknown default: | |
return .notDetermined | |
} | |
} | |
func request(then completion: @escaping (PermissionStatus) -> Void) { | |
if status == .notDetermined { | |
self.completion = completion | |
switch accessLevel { | |
case .whenInUse: | |
locationManager.requestWhenInUseAuthorization() | |
case .always: | |
locationManager.requestAlwaysAuthorization() | |
} | |
} else { | |
completion(status) | |
} | |
} | |
private let accessLevel: AccessLevel | |
private var completion: ((PermissionStatus) -> Void)? | |
init(accessLevel: AccessLevel) { | |
self.accessLevel = accessLevel | |
super.init() | |
locationManager.delegate = self | |
} | |
func locationManager(_ manager: CLLocationManager, | |
didChangeAuthorization status: CLAuthorizationStatus) { | |
completion?(self.status) | |
} | |
} | |
// MARK: - Location / When In Use | |
private class LocationWhenInUse: Location { | |
override func request(then completion: @escaping (PermissionStatus) -> Void) { | |
precondition(containsUsageDescription(forKey: "NSLocationWhenInUseUsageDescription")) | |
super.request(then: completion) | |
} | |
init() { | |
super.init(accessLevel: .whenInUse) | |
} | |
} | |
// MARK: - Location / Always | |
private class LocationAlways: Location { | |
override func request(then completion: @escaping (PermissionStatus) -> Void) { | |
precondition(containsUsageDescription(forKey: "NSLocationAlwaysUsageDescription")) | |
super.request(then: completion) | |
} | |
init() { | |
super.init(accessLevel: .always) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment