天天看點

iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)

前言

保護使用者的隐私不被侵害是每個開發者應該承擔的責任

使用者隐私已經不斷被資産化,通過商業運作成為了一種牟利的手段。apple在使用者隐私保護方面一直走在前列.​

​遵循使用最少的權限來實作功能​

例如給全部權限的話,有些app就偷偷讀相冊,根據相冊給推商品

在iOS14相冊 iOS 14 相冊權限增加了 Limited Photo 模式 ,新增選擇權限類型​

​ PHAuthorizationStatusLimited​

14)), // User has authorized this application for limited photo library access. Add PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YES to the application's Info.plist to prevent the automatic alert to update the users limited library selection. Use -[PHPhotoLibrary(PhotosUISupport) presentLimitedLibraryPickerFromViewController:] from PhotosUI/PHPhotoLibrary+PhotosUISupport.h to manually present the limited library picker.      
iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)

I iOS14相冊權限适配

預備知識:

冷啟動: App 不在記憶體中/沒有相關的程序存在

熱啟動:(從背景恢複)記憶體有部分,無相關的程序存在

暫停:存在記憶體中,存在相關程序

1.1 選擇允許被通路的圖檔資源

使用者在冷啟 APP 使用 PhotoKit 通路資源的時候會預設彈出系統圖檔選擇界面,讓使用者選擇允許被通路的資源:

當頁面彈出請求權限 Alert 時,會有​

​選擇照片...​

​選項,使用者選擇該選項時,會彈出頁面供使用者選擇允許App通路的照片。

iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)

後續有兩種方式來修改使用者選擇的資源:

  1. 手動觸發選擇/取消選擇圖檔以移除通路權限的界面 :​

    ​ [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];​

iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)
  1. 在應用設定->Photos->Edit Selected Photos中修改

部分相冊權限時,界面提供直接跳設定的入口

iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)
iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)

1.2 相冊權限API的相關改動

  • 新增了資源申請和擷取的 API,并且将老的資源申請 API 标為廢棄
/// Replaces \c +authorizationStatus to support add-only/read-write access level status
+ (PHAuthorizationStatus)authorizationStatusForAccessLevel:(PHAccessLevel)accessLevel API_AVAILABLE(macosx(11.0), ios(14), tvos(14));
+ (void)requestAuthorizationForAccessLevel:(PHAccessLevel)accessLevel handler:(void(^)(PHAuthorizationStatus status))handler API_AVAILABLE(macosx(11.0), ios(14), tvos(14)) NS_SWIFT_ASYNC(2);      
  • Limited Photo 模式:PHAuthorizationStatus 增加新的枚舉 PHAuthorizationStatusLimited
typedef NS_ENUM(NSInteger, PHAuthorizationStatus) {
    PHAuthorizationStatusNotDetermined = 0, // User has not yet made a choice with regards to this application
    PHAuthorizationStatusRestricted,        // This application is not authorized to access photo data.
                                            // The user cannot change this application’s status, possibly due to active restrictions
                                            //   such as parental controls being in place.
    PHAuthorizationStatusDenied,            // User has explicitly denied this application access to photos data.
    PHAuthorizationStatusAuthorized,        // User has authorized this application to access photos data.
    PHAuthorizationStatusLimited API_AVAILABLE(ios(14)), // User has authorized this application for limited photo library access. Add PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YES to the application's Info.plist to prevent the automatic alert to update the users limited library selection. Use -[PHPhotoLibrary(PhotosUISupport) presentLimitedLibraryPickerFromViewController:] from PhotosUI/PHPhotoLibrary+PhotosUISupport.h to manually present the limited library picker.      
  • 相冊通路方式: PHAccessLevel (請求查詢limited權限在 accessLevel 為 readAndWrite 時生效)
typedef NS_ENUM(NSInteger, PHAccessLevel) {
    PHAccessLevelAddOnly = 1,
    PHAccessLevelReadWrite = 2,
} API_AVAILABLE(macos(11.0), ios(14), tvos(14));      
iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)

1.3 适配demo

要點:APP 需要監聽使用者修改了允許通路的資源并且更新資源清單

private

  1. 手動觸發選擇更多照片或取消選擇以移除通路權限的界面:​

    ​ [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];​

  2. 顯示允許通路的相冊:​

    ​+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(PHFetchOptions *)options; ​

  3. 請求limited 權限:​

    ​requestAuthorizationForAccessLevel:handler:​

  4. 查詢權限相冊授權狀态:​

    ​+ (PHAuthorizationStatus)authorizationStatusForAccessLevel:(PHAccessLevel)accessLevel API_AVAILABLE(macosx(11.0), ios(14), tvos(14)); ​

  5. 圖檔選擇器:單選​

    ​UIImagePickerController​

  6. 圖檔選擇器: 多選​

    ​PHPickerViewController​

II 具體适配方案

2.1 手動觸發選擇/取消選擇圖檔以移除通路權限的界面

優化選擇圖檔彈窗提示的互動方式:官方建議關閉自動彈窗,推薦在使用者選擇了 Limited Photo 模式時,在特定界面(例如設定頁)顯示修改資源的入口,當使用者主動點選該入口時彈出系統彈窗。

  1. 關閉系統自動的選擇彈窗提示:可以在 info.plist 中設定 PHPhotoLibraryPreventAutomaticLimitedAccessAlert 選項為 YES ,
<key>NSPhotoLibraryUsageDescription</key>
  <string>你可以分享相機膠卷中的照片、将照片儲存</string>
  <key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
  <false/>      
  1. 手動觸發選擇更多照片或取消選擇以移除通路權限的界面:
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];      
iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)

2.2 相冊通路方式

還在使用 AssetsLibrary 管理資源讀寫的盡快遷移到 PhotoKit,對于隻讀,建議使用PHPicker。

3.2.1 隻讀權限

隻讀: 建議使用 iOS 14 提供的圖檔選擇器 PHPicker 來選擇圖檔資源(iOS 14 以下對應 UIImagePickerController)

PHPicker 優點:獨立程序,不影響 APP 性能。不需要使用者授予權限就可以選擇使用者所有的資源, 支援多選。

對應的代理協定如下

@interface ViewController () <UINavigationControllerDelegate, UIImagePickerControllerDelegate, PHPickerViewControllerDelegate>      

具體的實作代碼請看第三章節。

3.2.2 隻寫權限

需要在 Info.plist 下配置權限資訊

<key>NSPhotoLibraryAddUsageDescription</key>
    <string>你可以分享相機膠卷中的照片、将照片儲存</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>你可以分享相機膠卷中的照片</string>
    <key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
    <false/>      

分别使用​

​ AssetsLibrary​

​​、​

​PhotoKit​

​​ 以及 UIKit 層 ​

​UIImageWriteToSavedPhotosAlbum​

​ 寫入相冊

- (IBAction)saveClick:(UIButton *)sender {
    //參數1:圖檔對象
    //參數2:成功方法綁定的target
    //參數3:成功後調用方法
    //參數4:需要傳遞資訊(成功後調用方法的參數) 一般寫nil
    UIImageWriteToSavedPhotosAlbum(self.imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

    /* 1.先儲存圖檔到【相機膠卷】(不能直接儲存到自定義相冊中)
        1> C語言函數
        2> AssetsLibrary架構  (iOS4支援,iOS9.0被廢棄)
        3> Photos架構 (iOS 8.0支援 推薦使用)
       2.擁有一個【自定義相冊】
        1> AssetsLibrary架構
        2> Photos架構
       3.将剛才儲存到【相機膠卷】裡面的圖檔引用到【自定義相冊】
        1> AssetsLibrary架構
        2> Photos架構
     */

}
#pragma mark -- <儲存到相冊>
-(void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    NSString *msg = nil ;
    if(error){
        msg = @"儲存圖檔失敗" ;
    }else{
        msg = @"儲存圖檔成功"      
iOS小技能:iOS14相冊權限适配 (Limited Photo Library Access)

3.2.3 讀寫權限

分别使用 AssetsLibrary 和 PhotoKit 來讀取相冊資源

2.3 其他需要注意的API

2.4 監聽第一次相冊授權時

  1. 監聽到使用者點選不允許,不允許時顯示引導。
  2. 使用者未作出明确選擇的情況下自己主動請求了一次權限設定(重新整理UI的代碼放到主線程執行)
/**
 1. 監聽到使用者點選不允許,不允許時顯示引導
 2. 使用者未作出明确選擇的情況下自己主動請求了一次權限設定
 showAlert:不允許時顯示引導
 block: 允許之後的動作,比如儲存圖檔
 */
+(BOOL)isHasPhotoLibraryAuthorityWithisShowAlert:(BOOL)showAlert  block:(void (^)(id sender))block
{
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus] ;
//1.  定義局部block:  處理沒有權限的情況,顯示引導
    BOOL (^block4none)(PHAuthorizationStatus ) = ^ BOOL (PHAuthorizationStatus status ){
        NSLog(@" 沒有通路圖庫的權限==============");
        if (showAlert) {
            [LBAlertController showAlertTitle:@"無法使用相冊" content:@"請在iPhone的\"設定-隐私-照片\"中允許通路照片。" cancelString:@"取消" cancleBlock:nil sureString:@"去設定" sureBlock:^{
                // 需要在info.plist中添加 URL types 并設定一項URL Schemes為prefs  IOS10 以後不起作用
                    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]){
                    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
                }
            } currentController:[QCT_Common getCurrentVC]];
        }


        return NO;

    };
    switch (status) {
        case PHAuthorizationStatusRestricted:
        case PHAuthorizationStatusDenied : {

//沒有通路圖庫的權限
            return block4none(status);


        }
            break;

        case PHAuthorizationStatusNotDetermined:{//2. 使用者未作出明确選擇的情況下自己主動請求了一次權限設定
            [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus phStatus) {
                //監聽到使用者點選不允許,不允許時顯示引導
                if (phStatus == PHAuthorizationStatusRestricted || phStatus == PHAuthorizationStatusDenied) {

                    dispatch_sync(dispatch_get_main_queue(), ^{
                        //重新整理UI的代碼放到主線程執行
                        block4none(status);

                    });



                } else if (phStatus == PHAuthorizationStatusNotDetermined) {

                    // 不處理
                } else {
                    // 執行外圍的block
//                    status = QMUIAssetAuthorizationStatusAuthorized;

                    if(block){//執行允許之後的儲存圖檔操作
                        block(nil);
                    }

                }

            }];

            return  NO;

        }

        default:
            break;
    }
    if(block){// 3. 執行允許之後的儲存圖檔操作
        block(nil);
    }
    return  YES;
}      

III 選擇圖檔資源視圖

3.1 請求查詢權限

/**
 請求查詢權限
 */
- (IBAction)requestAuth:(id)sender
{

    // 請求權限,需注意 limited 權限盡在 accessLevel 為 readAndWrite 時生效
    PHAccessLevel level1 = PHAccessLevelAddOnly;// 僅允許添加照片
    PHAccessLevel level2 = PHAccessLevelReadWrite;// 允許通路照片,limitedLevel 必須為 readWrite
    [PHPhotoLibrary requestAuthorizationForAccessLevel:level2 handler:^(PHAuthorizationStatus status) {
      switch (status) {
      /**
        請求查詢限制權限:需注意 `PHAuthorizationStatusLimited` 權限在 accessLevel 為 `PHAccessLevelReadWrite` 時生效
      */
          case PHAuthorizationStatusLimited:
              NSLog(@"limited");
              break;
          case PHAuthorizationStatusDenied:
              NSLog(@"denied");
              break;
          case PHAuthorizationStatusAuthorized:
              NSLog(@"authorized");
              break;
          default:
              break;
      }
    }];
}      

3.2 使用UIImagePickerController選擇圖檔資源(單選)

  • 初始化
#pragma

/**
 UIImagePickerController
 */
- (UIImagePickerController *)picker
{
    if (!_picker) {
        _picker = [[UIImagePickerController alloc]init];
    }
    return _picker;
}


- (IBAction)openPickerAciton:(id)sender
{
    self.isDoing = NO;

    if (self.isDoing) {
        return;
    }

    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] == NO) {
        return;
    }
    self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    self.isDoing = YES;
    [self presentViewController:self.picker animated:YES completion:nil];
}      
  • 處理代理
void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    self.imageView.image = image;

    [picker dismissViewControllerAnimated:YES completion:nil];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:nil];
}      

3.3 使用PHPicker選擇圖檔資源(多選)

  • 初始化
#pragma


- (IBAction)openNewPicker:(id)sender
{
    //三種過濾類型
    PHPickerFilter *imagesFilter = PHPickerFilter.imagesFilter;
    PHPickerFilter *videosFilter = PHPickerFilter.videosFilter;
    PHPickerFilter *livePhotosFilter = PHPickerFilter.livePhotosFilter;

    PHPickerConfiguration *configuration = [[PHPickerConfiguration alloc] init];
    configuration.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[imagesFilter]]; // 可配置查詢使用者相冊中檔案的類型,支援三種
    configuration.selectionLimit = 0; // 預設為1,為0為跟随系統上限

    PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:configuration];
    picker.delegate = self;
    [self presentViewController:picker animated:YES completion:nil];
}      
  • 處理代理
void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14)) {
    [picker dismissViewControllerAnimated:YES completion:nil];
     if (!results || !results.count) {
         return;
     }
     NSItemProvider *itemProvider = results.firstObject.itemProvider;
     if ([itemProvider canLoadObjectOfClass:UIImage.class]) {
         __weak typeof(self) weakSelf = self;
         //異步擷取
         [itemProvider loadObjectOfClass:UIImage.class completionHandler:^(__kindof id<NSItemProviderReading>  _Nullable object, NSError * _Nullable error) {
             if ([object isKindOfClass:UIImage.class]) {
                 __strong typeof(self) strongSelf = weakSelf;
                 dispatch_async(dispatch_get_main_queue(), ^{
                     strongSelf.imageView.image = (UIImage      

see also