Swift PHPhotoLibrary authorization
TL;DR
some of realisation for IPHONEOS_DEPLOYMENT_TARGET v14
import UIKit
import PhotosUI
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.requestPhotoLibraryAccess()
}
func requestPhotoLibraryAccess() {
PHPhotoLibrary.requestAuthorization(for: .readWrite) { photoLibraryStatus in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.handlePhotoLibraryStatus(for: photoLibraryStatus)
}
}
}
func handlePhotoLibraryStatus(for status: PHAuthorizationStatus) {
switch status {
case .notDetermined:
break
case .authorized:
break
case .limited:
self.showAlertForPhotoLibraryLimited()
case .denied:
self.showAlertForPhotoLibraryDenied()
case .restricted:
self.showAlertForPhotoLibraryRestricted()
@unknown default:
break
}
}
func showAlertForPhotoLibraryLimited() {
let alert = UIAlertController(title: "Photo Library access is Limited", message: "Please Select more photos or Allow access to all photos in Settings", preferredStyle: .alert)
let action2 = UIAlertAction(title: "Select more photos", style: .default) { [weak self] _ in
guard let self = self else { return }
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self)
}
let action3 = UIAlertAction(title: "Keep Current Selection", style: .default)
alert.addAction(self.getOpenSettingsAlertAction())
alert.addAction(action2)
alert.addAction(action3)
self.present(alert, animated: true, completion: nil)
}
func showAlertForPhotoLibraryDenied() {
let alert = UIAlertController(title: "Photo Library access is Denied", message: "Please provide access to PhotoLibrary to use the app", preferredStyle: .alert)
alert.addAction(self.getOpenSettingsAlertAction())
self.present(alert, animated: true, completion: nil)
}
func showAlertForPhotoLibraryRestricted() {
let alert = UIAlertController(title: "Photo Library access is Restricted", message: "You are not able to grant such permission", preferredStyle: .alert)
self.present(alert, animated: true, completion: nil)
}
func getOpenSettingsAlertAction() -> UIAlertAction {
let action = UIAlertAction(title: "Open Settings", style: .default) { [weak self] _ in
guard let self = self else { return }
self.redirectToAppSettings()
}
return action
}
func redirectToAppSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(url) else {
print("not possible")
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
Explanation:
Provide description Info.plist
with NSPhotoLibraryUsageDescription
key which will be used in system's permission alert (inside message block) or you get next runtime error:
[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data
Info.plist
<key>NSPhotoLibraryUsageDescription</key>
<string>photos_access_description</string>
This alert is shown once when one of PHPhotoLibrary
function(e.g. PHPhotoLibrary.requestAuthorization(), PHPhotoLibrary.shared().register(), PHPhotoLibrary.authorizationStatus()...) is called and PHAuthorizationStatus
is .notDetermined
. When user made a decision and status now equal .notDetermined
it is only one way to change it - using App's Settings
PHAuthorizationStatus:
.notDetermined
- user has not make a choice. It is a default status before user set it explicitly via systems permission alert inside app.
.authorized
- user authorised the full access to PhotoLibrary via Allow Access to All Photos
or All Photos
.limited
- from iOS v14. User authorised the limited access to PhotoLibrary. User is able to provide a set of items via Select Photos...
or Selected Photos
. After that application will operate only selected items and this set is unique for every application
This status is provided only by PHPhotoLibrary.authorizationStatus(for:)
or PHPhotoLibrary.requestAuthorization(for:)
which are available from iOS v14. It means if user set .limited
access and you use PHPhotoLibrary.authorizationStatus
or PHPhotoLibrary.requestAuthorization
(even on iOS >= v14) you get .authorized
status
Every new app lifecycle when status is .limited
and a first access PhotoLibrary function is called (e.g PHAsset.fetchAssets()
) system automatically show alert to adjust selected items. Sometimes it is not comfortable
- To prevent this behaviour use next one in Info.plist:
<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
<string>YES</string>
- To show Selection Picker manually use:
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: vc)
photoLibraryDidChange()
is useful to control updates of selected items set. Please note that result of photoLibraryDidChange()
should be called in main thread
class MyViewController: UIViewController, PHPhotoLibraryChangeObserver {
override func viewDidLoad() {
super.viewDidLoad()
PHPhotoLibrary.shared().register(self)
}
func photoLibraryDidChange(_ changeInstance: PHChange) {
DispatchQueue.main.async { [weak self] in
//logic
}
}
}
*I used import PhotosUI
in other case I get Value of type 'PHPhotoLibrary' has no member 'presentLimitedLibraryPicker'
.denied
- user denied access via Don't Allow
or None
.restricted
- system restricted the access, for example Parental Control. To reproduce it: Settings -> Screen Time -> <Turn On Screen Time> -> Content & Privacy Restrictions -> Photos -> Don't Allow Changes
@unknown default
- for backward compatibility
Open App Settings programatically:
func redirectToAppSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(url) else {
print("not possible")
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
Please note that when user change this setting(status) in App's settings system automatically relaunches the app. But if status was not changed and it is important to control the status when user back to the app you can use NotificationCenter
. For example:
override func viewDidLoad() {
super.viewDidLoad()
//self.requestPhotoLibraryAccess()
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
@objc func didBecomeActive() {
self.requestPhotoLibraryAccess()
}
}
Result of PHPhotoLibrary.requestAuthorization()
should be called from main thread:
PHPhotoLibrary.requestAuthorization(for: .readWrite) { photoLibraryStatus in
DispatchQueue.main.async { [weak self] in
//logic
}
}