目录

  • 使用的库
  • 针对系统相册的操作
  • 请求相册访问权限
  • 获取当前的系统相册列表
  • 判断系统相册是否存指定文件夹
  • 创建相册目录
  • 保存图片到指定的目录
  • 保存视频到指定的相册中
  • 删除指定目录下的文件
  • 保存照片视频到系统相册
  • 检测当前资源是否为云存储
  • 加载相册中的照片/视频

使用的库

ios8 以前,使用的是<AssetsLibrary/AssetsLibrary.h>,ios8以后,苹果推出了<Photos/Photos.h> 现在一般使用photos库

针对系统相册的操作

文件处理思路,我们保存到系统相册还比较好处理,但是删除就有点麻烦了,因为我们存的文件名到了系统相册名称就已经变了,文件名也没有对外提供,但是提供了一个localIdentifier的字段,用来标识相册中的唯一元素,那么我们就需要将这个字段保存到plist或数据库,通过对应关系找到就可以进行删除了。

对相册的任何改变操作,都应该在

- (void)performChanges:(dispatch_block_t)changeBlock completionHandler:(nullable void(^)(BOOL success, NSError *__nullable error))completionHandler;

的changeBlock方法内部做处理

请求相册访问权限

plist 中需要配置的key

  • NSPhotoLibraryUsageDescription --- Privacy - Photo Library Usage Description
typedef NS_ENUM(NSInteger, PHAuthorizationStatus) {
    PHAuthorizationStatusNotDetermined = 0,
          // 用户还没有对这个应用程序作出选择
    PHAuthorizationStatusRestricted,        
        // 此应用程序未被授权访问照片数据.用户无法更改当前应用程序的状态
    PHAuthorizationStatusDenied,            
        // 用户明确拒绝了应用程序对照片数据的访问。
    PHAuthorizationStatusAuthorized,        
        // 用户已授权此应用程序访问照片数据。
    PHAuthorizationStatusLimited API_AVAILABLE(ios(14)), 
        // 用户已经授权使用有限的图片库.
        // 添加PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YES到应用程序的信息。防止plist自动提醒用户更新有限的库选择
        // 使用<PhotosUI/PHPhotoLibrary+PhotosUISupport.h>下的[PHPhotoLibrary(PhotosUISupport) presentLimitedLibraryPickerFromViewController:]来获取有限的库选择器
};
+ (BOOL)requestPhotoPrivacy
{
    // 获取授权状态
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
    if (status == PHAuthorizationStatusDenied) {
        // 如果拒绝做对应弹框处理
        return NO;
    }
    return YES;
}

获取当前的系统相册列表

对应的相册名

  • Favorites 收藏夹
  • Recents 最近
  • Recently Added 最近添加
  • Videos 视频
  • Panoramas 全景照片
  • Time-lapse 延时摄影
  • Slo-mo 慢动作
  • Screenshots 截屏
  • Portrait 人像
  • Live Photos 实时照片
  • Selfies 自拍
  • Animated 动画
  • Long Exposure 长曝光
  • Recently Deleted 最近删除
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
[smartAlbums enumerateObjectsUsingBlock:^(PHAssetCollection * _Nonnull collection, NSUInteger idx, BOOL * _Nonnull stop) {
    if ([collection isKindOfClass:[PHAssetCollection class]]) {
        PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:nil];
        
        NSLog(@"%@,count = %lu",collection.localizedTitle,(unsigned long)fetchResult.count);
    }
}];

判断系统相册是否存指定文件夹

- (BOOL)isExistFolder:(NSString *)folderName {
    //首先获取用户手动创建相册的集合
    PHFetchResult *collectonResuts = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
    
    __block BOOL isExisted = NO;
    //对获取到集合进行遍历
    [collectonResuts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        PHAssetCollection *assetCollection = obj;
        //folderName是我们写入照片的相册
        if ([assetCollection.localizedTitle isEqualToString:folderName])  {
            isExisted = YES;
        }
    }];
    
    return isExisted;
}

创建相册目录

- (void)createFolder:(NSString *)folderName {
    if (![self isExistFolder:folderName]) {
        [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
            //添加HUD文件夹
            [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:folderName];
            
        } completionHandler:^(BOOL success, NSError * _Nullable error) {
            if (success) {
                NSLog(@"创建相册文件夹成功!");
            } else {
                NSLog(@"创建相册文件夹失败:%@", error);
            }
        }];
    }
}

保存图片到指定的目录

- (void)saveImagePath:(NSString *)imagePath{
    NSURL *url = [NSURL fileURLWithPath:imagePath];
    
    //标识保存到系统相册中的标识
    __block NSString *localIdentifier;
    
    //首先获取相册的集合
    PHFetchResult *collectonResuts = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
    //对获取到集合进行遍历
    [collectonResuts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        PHAssetCollection *assetCollection = obj;
        //Camera Roll是我们写入照片的相册
        if ([assetCollection.localizedTitle isEqualToString:_folderName])  {
            [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
                //请求创建一个Asset
                PHAssetChangeRequest *assetRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url];
                // 设置创建时间
                assetRequest.creationDate = [NSDate date];
                //请求编辑相册
                PHAssetCollectionChangeRequest *collectonRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
                //为Asset创建一个占位符,放到相册编辑请求中
                PHObjectPlaceholder *placeHolder = [assetRequest placeholderForCreatedAsset];
                //相册中添加照片
                [collectonRequest addAssets:@[placeHolder]];
                
                localIdentifier = placeHolder.localIdentifier;
            } completionHandler:^(BOOL success, NSError *error) {
                if (success) {
                    NSLog(@"保存图片成功!");
                    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[self readFromPlist]];
                    [dict setObject:localIdentifier forKey:[self showFileNameFromPath:imagePath]];
                    [self writeDicToPlist:dict];
                } else {
                    NSLog(@"保存图片失败:%@", error);
                }
            }];
        }
    }];
}

保存视频到指定的相册中

- (void)saveVideoPath:(NSString *)videoPath {
    NSURL *url = [NSURL fileURLWithPath:videoPath];
    
    //标识保存到系统相册中的标识
    __block NSString *localIdentifier;
    
    //首先获取相册的集合
    PHFetchResult *collectonResuts = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
    //对获取到集合进行遍历
    [collectonResuts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        PHAssetCollection *assetCollection = obj;
        //folderName是我们写入照片的相册
        if ([assetCollection.localizedTitle isEqualToString:_folderName])  {
            [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
                //请求创建一个Asset
                PHAssetChangeRequest *assetRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];
                //请求编辑相册
                PHAssetCollectionChangeRequest *collectonRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
                //为Asset创建一个占位符,放到相册编辑请求中
                PHObjectPlaceholder *placeHolder = [assetRequest placeholderForCreatedAsset];
                //相册中添加视频
                [collectonRequest addAssets:@[placeHolder]];
                
                localIdentifier = placeHolder.localIdentifier;
            } completionHandler:^(BOOL success, NSError *error) {
                if (success) {
                    NSLog(@"保存视频成功!");
                    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[self readFromPlist]];
                    [dict setObject:localIdentifier forKey:[self showFileNameFromPath:videoPath]];
                    [self writeDicToPlist:dict];
                } else {
                    NSLog(@"保存视频失败:%@", error);
                }
            }];
        }
    }];
}

删除指定目录下的文件

- (void)deleteFile:(NSString *)filePath {
    // 判断目录是否存在
    if ([self isExistFolder:_folderName]) {
        //获取需要删除文件的localIdentifier
        NSDictionary *dict = [self readFromPlist];
        NSString *localIdentifier = [dict valueForKey:[self showFileNameFromPath:filePath]];
        // 获取相册列表
        PHFetchResult *collectonResuts = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
        // 遍历查找指定的相册
        [collectonResuts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            PHAssetCollection *assetCollection = obj;
            if ([assetCollection.localizedTitle isEqualToString:_folderName])  {
                // 获取对应相册下的照片列表
                PHFetchResult *assetResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:[PHFetchOptions new]];
                // 遍历寻找对应的标识
                [assetResult enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                    PHAsset *asset = obj;
                    if ([localIdentifier isEqualToString:asset.localIdentifier]) {
                        // 开始对相册改变
                        [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
                            // 删除对应的照片
                            [PHAssetChangeRequest deleteAssets:@[obj]];
                        } completionHandler:^(BOOL success, NSError *error) {
                            if (success) {
                                NSLog(@"删除成功!");
                                NSMutableDictionary *updateDic = [NSMutableDictionary dictionaryWithDictionary:dict];
                                [updateDic removeObjectForKey:[self showFileNameFromPath:filePath]];
                                [self writeDicToPlist:updateDic];
                            } else {
                                NSLog(@"删除失败:%@", error);
                            }
                        }];
                    }
                }];
            }
        }];
    }
}

保存照片视频到系统相册

- (BOOL)saveVideoToSystemMediaLibrary:(NSURL *)url AssetId:(NSString *)assetId {
    NSError *error = nil;

    BOOL result = [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
        // 创建改变请求
        PHAssetChangeRequest *assetRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];
        assetRequest.creationDate = [NSDate date];
        PHObjectPlaceholder *placeHolder = [assetRequest placeholderForCreatedAsset];
        // 在数据库中建立 自定义标识符和系统生成标识符关联关系
        [self.dataBase saveLocalIdentifier:placeHolder.localIdentifier WithAssetsId:assetId];
    } error:&error];
    // 移除对应的资源文件
    [[NSFileManager defaultManager]removeItemAtURL:url error:nil];
    return result && !error;
}

- (BOOL)saveImageToSystemMediaLibrary:(NSURL *)url AssetId:(NSString *)assetId {
    // 判断是否已经下载过对应的图片资源
    if (![self isDonwloadWithAssetId:assetId]) {
        NSError *error = nil;
        BOOL result = [[PHPhotoLibrary sharedPhotoLibrary]performChangesAndWait:^{
            // 创建添加图片请求
            PHAssetChangeRequest *assetRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url];
            assetRequest.creationDate = [NSDate date];
            PHObjectPlaceholder *placeHolder = [assetRequest placeholderForCreatedAsset];
            // 建立关联关系
            [self.dataBase saveLocalIdentifier:placeHolder.localIdentifier WithAssetsId:assetId];
        } error:&error];
        // 移除对应的资源
        [[NSFileManager defaultManager]removeItemAtURL:url error:nil];
        return result && !error;
    } else {
        [[NSFileManager defaultManager]removeItemAtURL:url error:nil];
        return YES;
    }
}

- (BOOL)isDonwloadWithAssetId:(NSString *)assetId {
    // 获取对应的标识符
    NSString *localIdentifier = [self.dataBase localIdentifierWithAssetId:assetId];
    if (!localIdentifier.length) {
        return NO;
    }
    // 查找是否存在对应的资源
    PHFetchResult *result = [PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil];
    if (result.count) {
        return YES;
    }
    return NO;
}

检测当前资源是否为云存储

+ (void)checkSystemAssetInCloud:(PHAsset *)asset complet:(void(^)(BOOL isInCloud))complet {
    //视频类型
    if (asset.mediaType == PHAssetMediaTypeVideo) {
        // 请求视频资源
        [[PHImageManager defaultManager] requestExportSessionForVideo:asset options:nil exportPreset:AVAssetExportPresetHighestQuality resultHandler:^(AVAssetExportSession * _Nullable exportSession, NSDictionary * _Nullable info) {
            // 获取的info中 PHImageResultIsInCloudKey 对应是否云存储
            dispatch_async(dispatch_get_main_queue(), ^{
                complet ? complet([info[PHImageResultIsInCloudKey] intValue] && !exportSession) : nil;
            });
        }];
    }
    
    // 图片类型
    if (asset.mediaType == PHAssetMediaTypeImage) {
        // 创建图片请求的option
        PHImageRequestOptions *originOptions = [[PHImageRequestOptions alloc]init];
        // 初始化相关的值
        originOptions.version = PHImageRequestOptionsVersionOriginal;
        originOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
        originOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
        // ios 13之后
        if (@available(iOS 13, *)) {
            // 通过option请求图片资源
            [[PHImageManager defaultManager]requestImageDataAndOrientationForAsset:asset options:originOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary * _Nullable info) {
                // 从info中获取是否云存储值
                dispatch_async(dispatch_get_main_queue(), ^{
                    complet ? complet([info[PHImageResultIsInCloudKey] intValue] && !imageData) : nil;
                });
            }];
        } else {
            // 13之前 
            [[PHImageManager defaultManager]requestImageDataForAsset:asset options:originOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
                // 从info中获取对应信息
                dispatch_async(dispatch_get_main_queue(), ^{
                    complet ? complet([info[PHImageResultIsInCloudKey] intValue] && !imageData) : nil;
                });
            }];
        }
    }
}

加载相册中的照片/视频

- (void)loadPhotosFromAlbum
{
    __weak AlbumViewController *albumVC = self;
    // 指定要加载的资源类型
    PHAssetMediaType assetType = PHAssetMediaTypeImage;
    if (self.type == MediaTypeVideo) {
        assetType = PHAssetMediaTypeVideo;
    }
    
    // 获取对应的资源列表
    assetsFetchResult = [PHAsset fetchAssetsWithMediaType:assetType options:nil];
    
    // 遍历资源对象
    [assetsFetchResult enumerateObjectsUsingBlock:^(PHAsset *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // 转换字符串
        NSString *createTimeString = [Utils compareDate:obj.creationDate];
        // 转换模型
        AlbumPhotoModel *photoModel = [[AlbumPhotoModel alloc] init];
        photoModel.isShowVideoBtn = NO;
        photoModel.createTime = createTimeString;
        photoModel.identifier = obj.localIdentifier;
        photoModel.importTime = [Utils compareDate:[Utils timeZoneToDate:[NSDate date]]];
        photoModel.importDate = [Utils timeZoneToDate:[NSDate date]];
        photoModel.asset = obj;
        photoModel.selected = NO;
        
        // 根据创建时间字符串分类存储
        NSMutableArray *data = [albumVC.assetDic objectForKey:createTimeString];
        if (data == nil) {      //6AA5251E-A38E-4B96-99B0-DCABD8ACE0D3/L0/001
            data = [NSMutableArray array];
            MediaSectionModel *mediaSectionModel = [[MediaSectionModel alloc] init];
            mediaSectionModel.sectionTimeString = createTimeString;
            mediaSectionModel.currentSectionState = false;
            mediaSectionModel.sectionTime = obj.creationDate;
            [albumVC.timeSections addObject:mediaSectionModel];
            [albumVC.assetDic setObject:data forKey:createTimeString];
        }
        [data addObject:photoModel];
        [albumVC.assets addObject:photoModel];
        
    }];
    //按时间逆序排序
//    self.timeSections = [[[albumVC.timeSections reverseObjectEnumerator] allObjects] mutableCopy];
    self.timeSections = [[Utils sortArray:self.timeSections withKey:@"sectionTime" ascending:false] mutableCopy];
    self.collectionView.sectionTitles = albumVC.timeSections;
    self.collectionView.albumDic = albumVC.assetDic;
    [self.collectionView reloadData];
}