• How to Manage Photo Library Permission in iOS


    When Apple introduced limited photo access in iOS 14, some iOS developers were not so happy about it, it seems like Apple was trying to make developers’ life harder. Any apps (old or new) that want to support iOS 14 or higher will have to handle this extra permission status.

    Meanwhile, from the user’s perspective, it is a very welcome change. Users can now have more control over the privacy of their photos, limiting the number of photos an app can access.

    With this new permission status, the way to handle photo library permission in iOS 14 is very different from the previous iOS versions. In this article, I would like to show you how to properly handle each and every permission status in your iOS apps. Once you get the full picture of how it works, you will find that it is not as complicated as you think.

    Note:

    This article is mainly for situations where you need to build your own image picker. If you don’t need a custom image picker, use the new PHPickerViewController (a better UIImagePickerController) that manages photo library permission for you.


    The Sample App

    In terms of photo library permission handling, I really like how Facebook App did it. Therefore, let’s try to create a simple prototype app that mimics the Facebook App’s photo library permission workflow.

    Since the aim of this article is to show you how to handle each permission status, we won’t be building a fully functioning image picker. Instead, we will create a POC image picker that displays the permission status, number of accessible photos, and the required UI elements.

    UIs for each authorization status

    When tap on the “See All Photos” button, the app will redirect the user to the app’s privacy settings so that he/she can grant full photo library access to the app.

    Tapping on “See All Photos” button

    Whereas, when tapping on the “Manage” button, the user can either select more photos or grant full photo library access to the app.

    Tapping on “Manage” button


    Requesting Photo Library Access Permission

    Before we can start accessing the user’s photo library, we must first get permission to do so. Head over to info.plist and add the NSPhotoLibraryUsageDescription key to the plist.

    Adding NSPhotoLibraryUsageDescription to info.plist

    Next up, import the PhotosUI module:

    import PhotosUI

    and insert the following code to our custom image picker viewDidLoad() method:

    1. // Request permission to access photo library
    2. PHPhotoLibrary.requestAuthorization(for: .readWrite) { [unowned self] (status) in
    3. DispatchQueue.main.async { [unowned self] in
    4. showUI(for: status)
    5. }
    6. }

    Do note that the request completion handler does not run on the main thread, thus it is our responsibility to dispatch back to the main thread when updating the view based not the returned permission status.

    Pro Tip:

    In the above code, we are requesting for .readWrite access level. If your app only requires write access, it is recommended to add the NSPhotoLibraryAddUsageDescription key into info.plist and request authorization using .addOnly access level.

    Within the showUI(for:) method, we will handle each and every possible permission status accordingly:

    1. func showUI(for status: PHAuthorizationStatus) {
    2. switch status {
    3. case .authorized:
    4. showFullAccessUI()
    5. case .limited:
    6. showLimittedAccessUI()
    7. case .restricted:
    8. showRestrictedAccessUI()
    9. case .denied:
    10. showAccessDeniedUI()
    11. case .notDetermined:
    12. break
    13. @unknown default:
    14. break
    15. }
    16. }

    Following are the brief description of each permission status:

    • .authorized: The app has full access to the photo library.
    • .limited: The app only has limited access to the photo library.
    • .restricted: The user is under a configuration profile (such as parental controls) that restricts the user from accessing the photo library. In other words, the user does not have the authority to grant photo library access.
    • .denied: The user does not allow the app to access the photo library.
    • .notDetermined: The user has not set the app’s authorization status.

    At this stage of implementation, if we try to show our custom image picker for the first time, it will prompt the user for photo library access permission.

    One good thing about the requestAuthorization(for:handler:) function is that it only prompts the users when permission status is .notDetermined. For the other permission status, the function will just trigger the completion handler by giving back the current permission status.


    Handling Access Denied

    If the user decided to stop our app from accessing the photo library for some reason, we can always encourage the user to change his/her mind.

    What we can do is to show a “See All Photos” button to redirect the user to the app’s privacy settings so that he/she can grant photo library access to the app.

    1. func showAccessDeniedUI() {
    2. manageButton.isHidden = true
    3. seeAllButton.isHidden = false
    4. infoLabel.text = "Status: denied"
    5. }

    By following the Facebook App workflow, when the “See All Photos” button is tapped, we will show a confirmation message telling the user what to do in order to grant photo library access to the app.

    1. @IBAction func seeAllButtonTapped(_ sender: Any) {
    2. let alert = UIAlertController(title: "Allow access to your photos",
    3. message: "This lets you share from your camera roll and enables other features for photos and videos. Go to your settings and tap \"Photos\".",
    4. preferredStyle: .alert)
    5. let notNowAction = UIAlertAction(title: "Not Now",
    6. style: .cancel,
    7. handler: nil)
    8. alert.addAction(notNowAction)
    9. let openSettingsAction = UIAlertAction(title: "Open Settings",
    10. style: .default) { [unowned self] (_) in
    11. // Open app privacy settings
    12. gotoAppPrivacySettings()
    13. }
    14. alert.addAction(openSettingsAction)
    15. present(alert, animated: true, completion: nil)
    16. }

    and the last part of the puzzle is to implement the gotoAppPrivacySettings() method that brings the user to the app’s privacy settings:

    1. func gotoAppPrivacySettings() {
    2. guard let url = URL(string: UIApplication.openSettingsURLString),
    3. UIApplication.shared.canOpenURL(url) else {
    4. assertionFailure("Not able to open App privacy settings")
    5. return
    6. }
    7. UIApplication.shared.open(url, options: [:], completionHandler: nil)
    8. }

    Here, we do not need to observe the change in the photo’s privacy settings. This is because every time when the user updates the photo’s privacy settings, our app will relaunch and start a brand new app life cycle.


    Handling Limited Access

    Managing Selected Photos

    When our app is in limited access mode, our app only has access to certain photos that are selected by the user. On top of that, in every app life cycle, iOS will automatically prompt a limited photos access alert the first time when the app tries to access the photo library.

    The limited photos access alert

    This is not what we want!

    We want to have a “Manage” button that allows the user to manually trigger the limited photo library picker when he/she wants to select more photos.

    In order to prevent the limited photos access alert from showing, add the PHPhotoLibraryPreventAutomaticLimitedAccessAlert key to info.plist and set its value to YES.

    Adding PHPhotoLibraryPreventAutomaticLimitedAccessAlert to info.plist

    After that, show the “Manage” button and the amount of photos we can access on screen.

    1. func showLimittedAccessUI() {
    2. manageButton.isHidden = false
    3. seeAllButton.isHidden = true
    4. let photoCount = PHAsset.fetchAssets(with: nil).count
    5. infoLabel.text = "Status: limited\nPhotos: \(photoCount)"
    6. }

    When the “Manage” button is tapped, we will show an action sheet, asking the user to either select more photos or grant full photo library access to the app.

    1. import PhotosUI
    2. @IBAction func manageButtonTapped(_ sender: Any) {
    3. let actionSheet = UIAlertController(title: "",
    4. message: "Select more photos or go to Settings to allow access to all photos.",
    5. preferredStyle: .actionSheet)
    6. let selectPhotosAction = UIAlertAction(title: "Select more photos",
    7. style: .default) { [unowned self] (_) in
    8. // Show limited library picker
    9. PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self)
    10. }
    11. actionSheet.addAction(selectPhotosAction)
    12. let allowFullAccessAction = UIAlertAction(title: "Allow access to all photos",
    13. style: .default) { [unowned self] (_) in
    14. // Open app privacy settings
    15. gotoAppPrivacySettings()
    16. }
    17. actionSheet.addAction(allowFullAccessAction)
    18. let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    19. actionSheet.addAction(cancelAction)
    20. present(actionSheet, animated: true, completion: nil)
    21. }

    With that, we have successfully implemented the workflow of managing selected photos. But how do we know that changes have been made so that we can update our custom image picker accordingly?

    Observing Photo Library Change

    In order to get notified when the selected photos changes, we must conform to the PHPhotoLibraryChangeObserver protocol and implement the photoLibraryDidChange(_:) method.

    1. extension ImagePickerViewController: PHPhotoLibraryChangeObserver {
    2. func photoLibraryDidChange(_ changeInstance: PHChange) {
    3. DispatchQueue.main.async { [unowned self] in
    4. // Obtain authorization status and update UI accordingly
    5. let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
    6. showUI(for: status)
    7. }
    8. }
    9. }

    Just like the request authorization completion handler, the photoLibraryDidChange(_:) method does not trigger on the main thread. Therefore, remember to dispatch back to the main thread before updating our custom image picker.

    With the conformance in place, all that remains is to register our custom image picker as an observer to the photo library change. Head back to the viewDidLoad() method and add the following line of code:

    1. // Observe photo library changes
    2. PHPhotoLibrary.shared().register(self)

    That’s it for observing photo library change. Our custom image picker should now be able to display the correct amount of selected photos every time selected photos changes.

    Updating UI after user selected new photos


    Handling Full Access

    As you can see from how we handle limited access, we do not straightaway present the limited library picker. Instead, we show an action sheet hinting the user to give us full library access. This is because getting full library access is the best case scenario and it is the easiest to handle.

    In our custom image picker, we can basically hide all the call to action buttons and just display the photo library content:

    1. func showFullAccessUI() {
    2. manageButton.isHidden = true
    3. seeAllButton.isHidden = true
    4. let photoCount = PHAsset.fetchAssets(with: nil).count
    5. infoLabel.text = "Status: authorized\nPhotos: \(photoCount)"
    6. }

    Handling Restricted Access

    As mentioned earlier, the restricted status indicates that even the user itself does not have access to the photo library. Meaning nothing can be done on the app side as well:

    1. func showRestrictedAccessUI() {
    2. manageButton.isHidden = true
    3. seeAllButton.isHidden = true
    4. infoLabel.text = "Status: restricted"
    5. }

    Wrapping Up

    There you have it! Properly handling every authorization status is not as complicated as you think. Feel free to get the full sample code here and try it for yourself.

    If you enjoy reading this article, please check out my other iOS development related articles. You can also follow me on Twitter, and subscribe to my monthly newsletter.

    Thanks for reading. 👨🏻‍💻

  • 相关阅读:
    Live800在线客服系统:跨越时空做生意,从每次互动开始
    FusionAccess软件架构、FusionAccess必须配置的四个组件、桌面发放流程、虚拟机组类型、桌面组类型
    3.71 OrCAD新建原理图时,每一个类目的含义是什么?OrCAD软件怎么显示元器件的封装名称?
    【小黑送书—第九期】>>重磅!这本30w人都在看的Python数据分析畅销书:更新了!
    Selenium入门
    Sentinel1.8.6自定义错误页
    Python潮流周刊#7:我讨厌用 asyncio
    NFT的中国化改良尝试
    FPGA之旅设计第五例-----IIC通信
    LaTeX 删除页码
  • 原文地址:https://blog.csdn.net/KAMILLE/article/details/126644493