Skip to content

Instantly share code, notes, and snippets.

@tadija
Last active October 5, 2021 12:28
Show Gist options
  • Save tadija/c20479d0012baaaacd699ecdc1511579 to your computer and use it in GitHub Desktop.
Save tadija/c20479d0012baaaacd699ecdc1511579 to your computer and use it in GitHub Desktop.
AEPermissions
/**
* 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