关于IOS中自定义相册的创建和照片存取
前段时间遇到一个需求,需要将游戏截图保存到手机相册,并在游戏中浏览这个相册照片的功能。游戏逻辑做个循环列表来显示,管理好照片内存的释放就可以了。主要是如何从手机相册中存取照片,安卓手机比较容易,拿到存储权限用IO读写就可以了,但是IOS对于相册这块做的比较封闭,需要用到一个Photos框架,通过这个框架去访问相册,并且本来一些常规的操作也比较麻烦,比如IOS的照片默认是没有自定义名字的,存储照片的时候IOS会默认用一个累计数字作为照片名存储,并且这个名字还不好获取,所以想通过名字来存取照片还有点麻烦的。
首先添加Photos框架
#import <Photos/Photos.h>
xcode中添加Photos.framework,并在头文件中引用。
获取权限
// 1. 获取当前App的相册授权状态
PHAuthorizationStatus authorizationStatus = [PHPhotoLibrary authorizationStatus];
// 2. 判断授权状态
if (authorizationStatus == PHAuthorizationStatusAuthorized) {
//已获得授权 doSomthing
} else if (authorizationStatus == PHAuthorizationStatusNotDetermined) {
// 没有授权过,弹出授权请求
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized) {
// 用户选择授权 doSomthing
} else {
// 用户选择拒绝 提示去设置界面 授权相册
NSLog(@"请在设置界面, 授权访问相册");
}
}];
}
else {
//已经拒绝授权 提示去设置界面 授权相册
NSLog(@"请在设置界面, 授权访问相册");
}
创建自定义相册
相册可能已创建,首相尝试获取相册,获取相册需要遍历当前所有相册,根据名字获取。
//定义相册名 和 相册引用
NSString *collectionName = @"自定义相册名"
__block PHAssetCollection *customAlbumCollection = nil;
遍历获取相册
if(!customAlbumCollection)
{
//先遍历所有相册 根据名字获取自定义相册
PHFetchResult<PHAssetCollection *> *results = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
for (PHAssetCollection *getCollection in results) {
if ([getCollection.localizedTitle isEqualToString:collectionName]) {
customAlbumCollection = getCollection;
}
}
}
没有找到,则创建一个自定义相册
//如果相册未创建
if(!customAlbumCollection)
{
__block NSString *collectionIdentifier = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
collectionIdentifier = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:collectionName].placeholderForCreatedAssetCollection.localIdentifier;
}completionHandler:^(BOOL success, NSError * _Nullable error) {
if(success)
{
customAlbumCollection = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[collectionIdentifier] options:nil].lastObject;
}else{
NSLog(@"创建相簿失败");
}
}];
}
因为对自定义相册的每个操作都需要先获取到相册引用,而每次获取相册都需要遍历,所以建议可以代码中缓存相册引用customAlbumCollection。
由于创建相册本身是个异步操作,而存储自定照片的时候可能会一下调用好几次,而每次都会尝试获取或创建自定义相册,这样就有可能导致重复创建(当然也可以在每次存储之前先检查,在回调里在存储照片,但是这个操作本身只需要做一次,每次都写在回调里比较麻烦,而且多一次对OC接口的调用,所以我选择在存储方法里检查并创建相册)。
OC里有一个信号机制,可以把一个异步操作挂起,等待接收到信号再执行。通过这个可以把创建相册的异步操作变成同步,每次存储的时候等待相册创建完成再存储。
if(!customAlbumCollection)
{
//要创建相册的时候先创建一个信号
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSString *collectionIdentifier = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
collectionIdentifier = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:collectionName].placeholderForCreatedAssetCollection.localIdentifier;
}completionHandler:^(BOOL success, NSError * _Nullable error) {
if(success)
{
customAlbumCollection = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[collectionIdentifier] options:nil].lastObject;
}else{
NSLog(@"创建相簿失败");
}
//异步回调结束发送信号
dispatch_semaphore_signal(semaphore);
}];
//等待接收信号
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
//后续再执行存储照片逻辑
//...
存储照片到自定义相册
IOS存储照片的流程是 先要将照片存储到系统默认相册,然后再保存到自定义相册,看起好像是所有照片实际都是存储在系统相册里的,自定义相册只是分类这么个意思?
由于没有找到直接从Unity传递图片二进制数据到OC的方法(有试过传递二进制参数,但是保存的时候失败了),所以我先把照片用IO保存到沙盒,再OC里用 NSData.dataWithContentOfFile获取到图片数据。
注意修改图片名 originalFilename,这个很重要,后面想要按照名字获取图片就需要通过这个originalFilename
使用PHAssetCreationRequest.addResourceWithType添加图片可以传递一个option参数,这个option里可设置修改originalFileName,试了其它保存图片的方法(PHAssetChangeRequest.creationRequestForAssetFromImage这些)都不能修改名字,最终存储下来的都是系统累计数字作为图片名。
NSString *readAddr = @"照片沙盒路径"
__block NSString *assetIdentifier = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 1.将图片保存到系统相册
PHAssetResourceCreationOptions *option = [[PHAssetResourceCreationOptions alloc] init];
//修改照片OriginalFilename为自定义名称
option.originalFilename = [readAddr lastPathComponent];
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAsset];
NSData *imgData = [NSData dataWithContentsOfFile:readAddr];
[request addResourceWithType:PHAssetResourceTypePhoto data:imgData options:option];
assetIdentifier = request.placeholderForCreatedAsset.localIdentifier;
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 3. 将相片保存到自定义的相册
PHAsset *asset = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetIdentifier] options:nil].lastObject;
PHAssetCollectionChangeRequest *requestCollection = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:customAlbumCollection];
// 添加进自定义的相册
[requestCollection addAssets:@[asset]];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
// todo 回调保存成功消息
NSLog(@"保存相册成功了");
// 回调Unity 照片存储成功
}
else {
NSLog(@"将相片保存到自定义的相薄失败:%@", error);
// 回调Unity 照片存储失败
}
}];
}
else {
NSLog(@"相片保存到相簿失败:%@", error);
// 回调Unity 照片存储失败
}
}];
根据图片名获取照片
获取照片同样要先获取到相册权限,基本上所有对相册的操作都要先获取权限,所以在获取相册之前也要先检查授权。
然后还要检查相册是否已创建,并获取到相册引用。
获取图片也是要先获取到自定义相册下的所有图片,然后遍历根据originalFilename进行比对查找(好像没有按照名字直接获取的办法)
NSString *picName = @"图片名"
// 获得相簿albumCollection中的所有PHAsset对象并存储在集合albumAssets中
PHFetchResult<PHAsset *> *albumAssets = [PHAsset fetchAssetsInAssetCollection:customAlbumCollection options:nil];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.resizeMode = PHImageRequestOptionsResizeModeNone;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
// 同步获得图片, 只会返回1张图片
options.synchronous = NO;
options.networkAccessAllowed = YES;
// 遍历集合, 并获取文件图片及其他信息
for (PHAsset *asset in albumAssets) {
// mediaType文件类型
// PHAssetMediaTypeUnknown = 0, 位置类型
// PHAssetMediaTypeImage = 1, 图片
// PHAssetMediaTypeVideo = 2, 视频
// PHAssetMediaTypeAudio = 3, 音频
if (asset.mediaType == PHAssetMediaTypeImage){
//NSString *fileName = [asset valueForKey:@"filename"];//系统命名
NSArray<PHAssetResource *> *resources = [PHAssetResource assetResourcesForAsset:(PHAsset *)asset];
PHAssetResource * resuorce = [resources firstObject];
if(resuorce)
{
NSString *originalName = resuorce.originalFilename;
//NSLog(@"imgOriginalName:%@", originalName);
//比对图片名
if([originalName isEqualToString:picName]){
// 获取文件图片
// 是否要原图
//CGSize size = originSize ? CGSizeMake(asset.pixelWidth, asset.pixelHeight) : CGSizeZero;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
NSData *imgData = UIImagePNGRepresentation(result);
if(imgData == nil)
{
imgData= UIImageJPEGRepresentation(result, .6);
}
NSString *_encodeImageStr = [imgData base64EncodedStringWithOptions:0];
//TODO 将图片二进制发回unity
}];
return;
}
}
}
}
之前在部分手机上发现获取到的图片模糊的情况,后来通过修改PHImageRequestOptions的参数设置改为:
options.resizeMode = PHImageRequestOptionsResizeModeNone;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
并将requestImageForAsset的targetSize改为PHImageManagerMaximumSize
得到解决。
如果需要获取缩略图,可以设置为
options.resizeMode = PHImageRequestOptionsResizeModeFast;
options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
options.networkAccessAllowed = YES这个参数用来控制是否从iCloud下载照片,因为有些图片可能本地没有原图,而是存储在iCloud库中,开启这个选项可以允许从iCloud中下载。
关于PHImageRequestOptions的几个参数:
synchronous = YES. 同步。只返回一张图片并且deliveryMode会忽略用户设置的值,直接设为 PHImageRequestOptionsDeliveryModeHighQualityFormat。
a. resizeMode: PHImageRequestOptionsResizeModeNone: 返回的是原图大小
b. resizeMode: PHImageRequestOptionsResizeModeFast: 当原图是压缩图时,会使用targetSize来最优解码图片,获得的图片大小可能比targetSize大
c. resizeMode: PHImageRequestOptionsResizeModeExact: 解压和Fast一样,但是返回的是指定targetSize的高质量图
synchronous: NO. 异步。
a. deliveryMode: PHImageRequestOptionsDeliveryModeOpportunistic: 会返回多张图片
(1). PHImageRequestOptionsResizeModeNone: 先返回低清的缩略图,再返回原图大小
(2). PHImageRequestOptionsResizeModeFast: 先返回低清的缩略图,再返回的图片如 1-b 一样
(3). PHImageRequestOptionsResizeModeExact: 先返回低清的缩略图,再返回的图片如 1-c一样
b. deliveryMode: PHImageRequestOptionsDeliveryModeHighQualityFormat: 只会返回一张高清图片
(1). PHImageRequestOptionsResizeModeNone: 如 1-a 一样
(2). PHImageRequestOptionsResizeModeFast: 如 1-b 一样
(3). PHImageRequestOptionsResizeModeExact: 如 1-c一样
c. deliveryMode: PHImageRequestOptionsDeliveryModeFastFormat: 只会返回一张图片,并且可能是低清图
(1). PHImageRequestOptionsResizeModeNone: 返回一张低清图
(2). PHImageRequestOptionsResizeModeFast: 返回一张低清图
(3). PHImageRequestOptionsResizeModeExact: 返回一张低清图
- 上一篇: 关于RectMask2D裁剪粒子特效的一种便捷方案
- 下一篇: 一个支持UI界面分块分帧加载编辑的组件