jsdw_ios/QuickLocation/Manager/App/Authorize.swift

431 lines
15 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Authorize.swift
// DLSDK
//
// Created by osell on 2023/8/3.
//
import UIKit
import CoreLocation
import Photos
import AVFoundation
import UserNotifications
import AdSupport
import AppTrackingTransparency
import URLNavigator
import Contacts
// MARK: - AuthorizeType
/// 权限类型枚举
@objc(DLAuthorizeType)
public enum AuthorizeType: Int {
/// 使用时定位,Info.plist需配置NSLocationWhenInUseUsageDescription
case locationWhenInUse = 1
/// 后台定位,Info.plist需配置NSLocationAlwaysUsageDescription和NSLocationAlwaysAndWhenInUseUsageDescription
case locationAlways = 2
/// 麦克风,需启用Microphone子模块,Info.plist需配置NSMicrophoneUsageDescription
case microphone = 3
/// 相册,Info.plist需配置NSPhotoLibraryUsageDescription
case photoLibrary = 4
/// 照相机,Info.plist需配置NSCameraUsageDescription
case camera = 5
/// 联系人,需启用Contacts子模块,Info.plist需配置NSContactsUsageDescription
case contacts = 6
/// 日历,需启用Calendar子模块,Info.plist需配置NSCalendarsUsageDescription
case calendars = 7
/// 提醒,需启用Calendar子模块,Info.plist需配置NSRemindersUsageDescription
case reminders = 8
/// 音乐,需启用AppleMusic子模块,Info.plist需配置NSAppleMusicUsageDescription
case appleMusic = 9
/// 通知,远程推送需打开Push Notifications开关和Background Modes的Remote notifications开关
case notifications = 10
/// 广告跟踪,需启用Tracking子模块,Info.plist需配置NSUserTrackingUsageDescription
case tracking = 11
}
// MARK: - AuthorizeStatus
/// 权限状态枚举
@objc(DLAuthorizeStatus)
public enum AuthorizeStatus: Int {
/// 未确认
case notDetermined = 0
/// 受限制
case restricted = 1
/// 被拒绝
case denied = 2
/// 已授权
case authorized = 3
}
// MARK: - AuthorizeProtocol
/// 权限授权协议
@objc(DLAuthorizeProtocol)
public protocol AuthorizeProtocol {
/// 查询权限状态,必须实现。某些权限会阻塞当前线程,建议异步查询,如通知
func authorizeStatus() -> AuthorizeStatus
/// 执行权限授权,主线程回调,必须实现
func authorize(_ completion: ((AuthorizeStatus) -> Void)?)
/// 异步查询权限状态,当前线程回调,可选实现。某些权限建议异步查询,不会阻塞当前线程,如通知
@objc optional func authorizeStatus(_ completion: ((AuthorizeStatus) -> Void)?)
}
// MARK: - AuthorizeManager
/// 权限管理器。由于打包上传ipa时会自动检查隐私库并提供Info.plist描述,所以默认关闭隐私库声明
///
@objc(DLAuthorizeManager)
@objcMembers public class AuthorizeManager: NSObject {
private static var managers: [AuthorizeType: AuthorizeProtocol] = [:]
private static var blocks: [AuthorizeType: () -> AuthorizeProtocol] = [:]
/// 获取指定类型的权限管理器单例,部分权限未启用时返回nil
public static func manager(type: AuthorizeType) -> AuthorizeProtocol? {
if let manager = managers[type] { return manager }
guard let manager = factory(type: type) else { return nil }
managers[type] = manager
return manager
}
/// 注册指定类型的权限管理器创建句柄,用于动态扩展权限类型
public static func registerAuthorize(_ type: AuthorizeType, withBlock block: @escaping () -> AuthorizeProtocol) {
blocks[type] = block
}
private static func factory(type: AuthorizeType) -> AuthorizeProtocol? {
if let block = blocks[type] {
return block()
}
switch type {
case .locationWhenInUse:
return AuthorizeLocation(isAlways: false)
case .locationAlways:
return AuthorizeLocation(isAlways: true)
case .photoLibrary:
return AuthorizePhotoLibrary()
case .camera:
return AuthorizeCamera()
case .notifications:
return AuthorizeNotifications()
case .microphone:
return AuthorizeMicrophone()
case .tracking:
return AuthorizeTracking()
case .contacts:
return AuthorizeContacts()
default:
return nil
}
}
static func openAppSetting(title: String, message: String) {
let alertVC = UIAlertController.init(title: title,
message: message,
preferredStyle: .alert)
let cancelAction = UIAlertAction.init(title: "取消", style: .cancel)
let setAction = UIAlertAction.init(title: "去设置", style: .default) { action in
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsURL) {
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
}
}
alertVC.addAction(cancelAction)
alertVC.addAction(setAction)
UIViewController.topMost?.present(alertVC, animated: true, completion: nil)
}
}
// MARK: - AuthorizeLocation
/// 定位授权
private class AuthorizeLocation: NSObject, AuthorizeProtocol, CLLocationManagerDelegate {
private lazy var locationManager: CLLocationManager = {
let result = CLLocationManager()
result.delegate = self
return result
}()
private var completionBlock: ((AuthorizeStatus) -> Void)?
private var changeIgnored: Bool = false
private var isAlways: Bool = false
init(isAlways: Bool) {
super.init()
self.isAlways = isAlways
}
func authorizeStatus() -> AuthorizeStatus {
// 定位功能未打开时返回Denied,可自行调用[CLLocationManager locationServicesEnabled]判断
let status = CLLocationManager.authorizationStatus()
switch status {
case .restricted:
return .restricted
case .denied:
return .denied
case .authorizedAlways:
return .authorized
case .authorizedWhenInUse:
if isAlways {
let isAuthorized = UserDefaults.standard.object(forKey: "DLAuthorizeLocation")
return isAuthorized != nil ? .denied : .notDetermined
} else {
return .authorized
}
default:
return .notDetermined
}
}
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
completionBlock = completion
if isAlways {
locationManager.requestAlwaysAuthorization()
// 标记已请求授权
UserDefaults.standard.set(NSNumber(value: 1), forKey: "DLAuthorizeLocation")
UserDefaults.standard.synchronize()
} else {
locationManager.requestWhenInUseAuthorization()
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// 如果请求always权限且当前已经是WhenInUse权限,系统会先回调此方法一次,忽略之
if isAlways && !changeIgnored {
if status == .notDetermined || status == .authorizedWhenInUse {
changeIgnored = true
return
}
}
// 主线程回调,仅一次
if completionBlock != nil {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.completionBlock?(self.authorizeStatus())
self.completionBlock = nil
}
}
}
}
// MARK: - AuthorizePhotoLibrary
/// 相册授权
private class AuthorizePhotoLibrary: NSObject, AuthorizeProtocol {
func authorizeStatus() -> AuthorizeStatus {
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .restricted:
return .restricted
case .denied:
return .denied
case .authorized:
return .authorized
default:
return .notDetermined
}
}
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
PHPhotoLibrary.requestAuthorization { status in
if completion != nil {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
completion?(self.authorizeStatus())
}
}
}
}
}
// MARK: - AuthorizeCamera
/// 照相机授权
private class AuthorizeCamera: NSObject, AuthorizeProtocol {
func authorizeStatus() -> AuthorizeStatus {
// 模拟器不支持照相机,返回受限制
let mediaType = AVMediaType.video
guard let device = AVCaptureDevice.default(for: mediaType) else { return .restricted }
if !device.hasMediaType(mediaType) { return .restricted }
let status = AVCaptureDevice.authorizationStatus(for: mediaType)
switch status {
case .restricted:
return .restricted
case .denied:
return .denied
case .authorized:
return .authorized
default:
return .notDetermined
}
}
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
AVCaptureDevice.requestAccess(for: .video) { granted in
if completion != nil {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
completion?(self.authorizeStatus())
}
}
}
}
}
// MARK: - AuthorizeNotifications
/// 通知授权
private class AuthorizeNotifications: NSObject, AuthorizeProtocol {
func authorizeStatus() -> AuthorizeStatus {
var status: AuthorizeStatus = .notDetermined
// 由于查询授权为异步方法,此处使用信号量阻塞当前线程,同步返回查询结果
let semaphore = DispatchSemaphore(value: 0)
UNUserNotificationCenter.current().getNotificationSettings { settings in
switch settings.authorizationStatus {
case .denied:
status = .denied
case .authorized:
status = .authorized
case .provisional:
status = .authorized
default:
status = .notDetermined
}
semaphore.signal()
}
semaphore.wait()
return status
}
func authorizeStatus(_ completion: ((AuthorizeStatus) -> Void)?) {
UNUserNotificationCenter.current().getNotificationSettings { settings in
var status: AuthorizeStatus = .notDetermined
switch settings.authorizationStatus {
case .denied:
status = .denied
case .authorized:
status = .authorized
case .provisional:
status = .authorized
default:
status = .notDetermined
}
if completion != nil {
DispatchQueue.main.async {
completion?(status)
}
}
}
}
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
let options: UNAuthorizationOptions = [.badge, .sound, .alert]
UNUserNotificationCenter.current().requestAuthorization(options: options) { granted, error in
let status: AuthorizeStatus = granted ? .authorized : .denied
if completion != nil {
DispatchQueue.main.async {
completion?(status)
}
}
}
UIApplication.shared.registerForRemoteNotifications()
}
}
// MARK: - AuthorizeMicrophone
/// 麦克风授权
private class AuthorizeMicrophone: NSObject, AuthorizeProtocol {
func authorizeStatus() -> AuthorizeStatus {
let status = AVAudioSession.sharedInstance().recordPermission
switch status {
case .denied:
return .denied
case .granted:
return .authorized
default:
return .notDetermined
}
}
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
AVAudioSession.sharedInstance().requestRecordPermission { granted in
let status: AuthorizeStatus = granted ? .authorized : .denied
if completion != nil {
DispatchQueue.main.async {
completion?(status)
}
}
}
}
}
// MARK: - AuthorizeTracking
/// IDFA授权,iOS14+使用AppTrackingTransparency,其它使用AdSupport
class AuthorizeTracking: NSObject, AuthorizeProtocol {
func authorizeStatus() -> AuthorizeStatus {
if #available(iOS 14.0, *) {
let status = ATTrackingManager.trackingAuthorizationStatus
switch status {
case .restricted:
return .restricted
case .denied:
return .denied
case .authorized:
return .authorized
default:
return .notDetermined
}
} else {
return ASIdentifierManager.shared().isAdvertisingTrackingEnabled ? .authorized : .denied
}
}
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
if #available(iOS 14.0, *) {
ATTrackingManager.requestTrackingAuthorization { status in
if completion != nil {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
completion?(self.authorizeStatus())
}
}
}
} else {
if completion != nil {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
completion?(self.authorizeStatus())
}
}
}
}
}
// MARK: - AuthorizeContacts
private class AuthorizeContacts: NSObject, AuthorizeProtocol {
func authorizeStatus() -> AuthorizeStatus {
switch CNContactStore.authorizationStatus(for: .contacts) {
case .restricted:
return .restricted
case .denied:
return .denied
case .authorized:
return .authorized
default:
return .notDetermined
}
}
func authorize(_ completion: ((AuthorizeStatus) -> Void)?) {
CNContactStore().requestAccess(for: .contacts) { _, _ in
DispatchQueue.main.async { completion?(self.authorizeStatus()) }
}
}
}