前言
保護使用者的隐私不被侵害是每個開發者應該承擔的責任
使用者隐私已經不斷被資産化,通過商業運作成為了一種牟利的手段。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.

I iOS14相冊權限适配
預備知識:
冷啟動: App 不在記憶體中/沒有相關的程序存在
熱啟動:(從背景恢複)記憶體有部分,無相關的程序存在
暫停:存在記憶體中,存在相關程序
1.1 選擇允許被通路的圖檔資源
使用者在冷啟 APP 使用 PhotoKit 通路資源的時候會預設彈出系統圖檔選擇界面,讓使用者選擇允許被通路的資源:
當頁面彈出請求權限 Alert 時,會有
選擇照片...
選項,使用者選擇該選項時,會彈出頁面供使用者選擇允許App通路的照片。
後續有兩種方式來修改使用者選擇的資源:
- 手動觸發選擇/取消選擇圖檔以移除通路權限的界面 :
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];
- 在應用設定->Photos->Edit Selected Photos中修改
部分相冊權限時,界面提供直接跳設定的入口
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));
1.3 适配demo
要點:APP 需要監聽使用者修改了允許通路的資源并且更新資源清單
private
- 手動觸發選擇更多照片或取消選擇以移除通路權限的界面:
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];
- 顯示允許通路的相冊:
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(PHFetchOptions *)options;
- 請求limited 權限:
requestAuthorizationForAccessLevel:handler:
- 查詢權限相冊授權狀态:
+ (PHAuthorizationStatus)authorizationStatusForAccessLevel:(PHAccessLevel)accessLevel API_AVAILABLE(macosx(11.0), ios(14), tvos(14));
- 圖檔選擇器:單選
UIImagePickerController
- 圖檔選擇器: 多選
PHPickerViewController
II 具體适配方案
2.1 手動觸發選擇/取消選擇圖檔以移除通路權限的界面
優化選擇圖檔彈窗提示的互動方式:官方建議關閉自動彈窗,推薦在使用者選擇了 Limited Photo 模式時,在特定界面(例如設定頁)顯示修改資源的入口,當使用者主動點選該入口時彈出系統彈窗。
- 關閉系統自動的選擇彈窗提示:可以在 info.plist 中設定 PHPhotoLibraryPreventAutomaticLimitedAccessAlert 選項為 YES ,
<key>NSPhotoLibraryUsageDescription</key>
<string>你可以分享相機膠卷中的照片、将照片儲存</string>
<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
<false/>
- 手動觸發選擇更多照片或取消選擇以移除通路權限的界面:
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];
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 = @"儲存圖檔成功"
3.2.3 讀寫權限
分别使用 AssetsLibrary 和 PhotoKit 來讀取相冊資源
2.3 其他需要注意的API
2.4 監聽第一次相冊授權時
- 監聽到使用者點選不允許,不允許時顯示引導。
- 使用者未作出明确選擇的情況下自己主動請求了一次權限設定(重新整理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