在上一部分简单的了解了一下资源PHAsset、相册PHAssetCollection、相册文件夹PHCollectionList,顺便简单的了解了一下通过PHPhotoLibrary判断和请求访问权限,这一部分将在上一部分的基础上进一步学习和了解其他的内容。
其实,如果从分类上来说,上一部分的内容主要是解决了数据的访问问题,有了数据,就可以开始增删改查等一系列操作了。
对于单个资源的操作主要有三种:
typedef NS_ENUM(NSInteger, PHAssetEditOperation) {
PHAssetEditOperationDelete = 1, // 删除
PHAssetEditOperationContent = 2, // 修改内容
PHAssetEditOperationProperties = 3, //修改属性
}
PHAssetChangeRequest
在该类的刚开始的地方,有一句绿色英文,如下:
// PHAssetChangeRequest can only be created or used within a -[PHPhotoLibrary performChanges:] or -[PHPhotoLibrary performChangesAndWait:] block.
这句话是说,该类只能在[PHPhotoLibrary performChanges:] 或者 -[PHPhotoLibrary performChangesAndWait:]中使用。
该类用来创建一个请求,用来给相册添加、删除或者编辑asset。
- 创建和添加图片和视频
+ (instancetype)creationRequestForAssetFromImage:(UIImage *)image;
+ (nullable instancetype)creationRequestForAssetFromImageAtFileURL:(NSURL *)fileURL;
+ (nullable instancetype)creationRequestForAssetFromVideoAtFileURL:(NSURL *)fileURL;
这里有三个创建和添加方法,通俗易懂,不再做更多的说明了。
- (IBAction)toCreateAndAndAsset:(id)sender {
// [identifierArr removeAllObjects];
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
NSLog(@"ggggggggg");
PHAssetChangeRequest * request = [PHAssetChangeRequest creationRequestForAssetFromImage:[UIImage imageNamed:@"girl.jpg"]];
if (request.placeholderForCreatedAsset.localIdentifier) {
[identifierArr addObject:request.placeholderForCreatedAsset.localIdentifier];
}
} completionHandler:^(BOOL success, NSError * _Nullable error) {
NSLog(@"ffffffff");
if (success) {
NSLog(@"添加成功!");
} else {
NSLog(@"添加失败!");
}
}];
// NSLog(@"hhhhhhh");
// BOOL isSuccess = [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
// [PHAssetChangeRequest creationRequestForAssetFromImage:[UIImage imageNamed:@"girl.jpg"]];
// } error:nil];
// if (isSuccess) {
// NSLog(@"添加成功!");
// } else {
// NSLog(@"添加失败!");
// }
// NSLog(@"iiiiiiii");
}
除此之外,PHAssetChangeRequest还有个子类PHAssetCreationRequest也可以用来添加,该子类丰富了父类的可添加方式。举个例子就好:
- (IBAction)toUseAssetCreationRequestAdd:(id)sender {
BOOL isSupport = [PHAssetCreationRequest supportsAssetResourceTypes:@[@(PHAssetResourceTypeFullSizePhoto)]]; // 不支持
if (isSupport) {
NSLog(@"支持");
}
NSURL * url1 = [[NSBundle mainBundle] URLForResource:@"girl" withExtension:@"jpg"];
// NSURL * url2 = [[NSBundle mainBundle] URLForResource:@"code" withExtension:@"png"];
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest * request = [PHAssetCreationRequest creationRequestForAsset];
request.favorite = YES;
[request addResourceWithType:PHAssetResourceTypePhoto fileURL:url1 options:nil];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"添加成功");
} else {
NSLog(@"添加失败");
}
}];
}
这里需要注意的是,经过检查发现,这里支持的ResourceType类型只有PHAssetResourceTypePhoto和PHAssetResourceTypeVideo。
- 删除图片和视频
调用方法+ (void)deleteAssets:(id
删除,这里的参数是包含PHAsset对象的遵循NSFastEnumeration协议的对象。)assets;
除此之外,更安全的操作是应先使用PHAsset的方法- (BOOL)canPerformEditOperation:(PHAssetEditOperation)editOperation;
判断一下该资源是否可以被删除。
- (IBAction)toDeleteAsset:(id)sender {
if (identifierArr.count == 0) {
UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"提示" message:@"没有要删除的目标,请先添加资源!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alertVC addAction:okAction];
[self presentViewController:alertVC animated:YES completion:nil];
return;
}
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 查找set
PHFetchResult * result = [PHAsset fetchAssetsWithLocalIdentifiers:identifierArr options:nil];
[PHAssetChangeRequest deleteAssets:result];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"删除成功!");
} else {
NSLog(@"删除失败:%@", error);
}
}];
}
这里的删除是可交互式的,如下图所示:
- 更新图片和视频属性
名义上是更新,但是也只能更新四个内容:创建日期、拍照地点、是否收藏、是否隐藏。
同样的,在做这些属性修改之前,应先做一次判断最为合适。
这里以修改是否已收藏属性为例。
- (IBAction)toModifyAsset:(id)sender {
if (identifierArr.count == 0) {
UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"提示" message:@"没有要更新的目标,请先添加资源!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alertVC addAction:okAction];
[self presentViewController:alertVC animated:YES completion:nil];
return;
}
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHFetchResult * fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:identifierArr options:nil];
if (fetchResult.count > 0) {
PHAsset * firstAsset = fetchResult.firstObject;
PHAssetChangeRequest * changeRequest = [PHAssetChangeRequest changeRequestForAsset:firstAsset];
changeRequest.favorite = YES;
}
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"修改成功");
} else {
NSLog(@"修改失败");
}
}];
}
- 编辑图片和视频的内容
通过类方法+ (instancetype)changeRequestForAsset:(PHAsset *)asset;
获取到PHAssetChangeRequest对象,然后修改其中的属性contentEditingOutput
即可,当想恢复原来的样子的时候,调用方法- (void)revertAssetContentToOriginal;
即可。
在操作修改内容之前,还是有必要先检查一下是否可以修改内容。
对于编辑的相关步骤如下:
简单来说就四步:
1、查找到资源PHAsset
2、调用方法去获取PHContentEditingInput对象
3、通过申请你要对asset做的修改
4、初始化PHContentEditingOutput对象或者PHLivePhotoEditingContext对象
5、使用PHPhotoLibrary的block,在该block中创建PHAssetChangeRequest对象,并把上面的PHContentEditingOutput对象赋值给PHAssetChangeRequest对象的contentEditingOutput属性
具体的代码如下:
- (IBAction)toEditAssetContent:(id)sender {
// 1、根据identifier查找到asset
PHFetchResult * fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:identifierArr options:nil];
if (fetchResult.count > 0) {
PHAsset * firstAsset = fetchResult.firstObject;
if ([firstAsset canPerformEditOperation:PHAssetEditOperationContent]) {
NSLog(@"可以被修改");
}
// 2、获取PHContentEditingInput对象
PHContentEditingInputRequestOptions * requestOptions = [[PHContentEditingInputRequestOptions alloc] init];
// 该方法只有在已经存在PHAdjustmentData的情况下才会被调用
requestOptions.canHandleAdjustmentData = ^BOOL(PHAdjustmentData * _Nonnull adjustmentData) {
NSLog(@"formatIdentifier:%@,formatVersion:%@", adjustmentData.formatIdentifier, adjustmentData.formatVersion);
return YES;
};
// PHContentEditingInputRequestID requestId =
[firstAsset requestContentEditingInputWithOptions:requestOptions completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
// 3、创建PHAdjustmentdata,可以在data参数里设置要做的操作,比如过滤等,可以是一个序列串,但是这里的长度是有限制的
PHAdjustmentData * adjustData = [[PHAdjustmentData alloc] initWithFormatIdentifier:firstAsset.localIdentifier formatVersion:@"1.0" data:[@"ceshi" dataUsingEncoding:NSUTF8StringEncoding]];
// 4、初始化PHContentEditingOutput对象
PHContentEditingOutput * output = [[PHContentEditingOutput alloc] initWithContentEditingInput:contentEditingInput];
output.adjustmentData = adjustData;
// 通过PHContentEditingInput可以获取到想要的信息,处理图片:修改图片整体颜色
NSURL * imageUrl = [contentEditingInput fullSizeImageURL]; // 包含图片数据的文件
NSData * originalImageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage * originalImage = [UIImage imageWithData:originalImageData];
CGImageRef imageRef = originalImage.CGImage;
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
uint32_t bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
//获取每个像素的字节数据集合
CGDataProviderRef dataProviderRef = CGImageGetDataProvider(imageRef);
CFDataRef dataRef = CGDataProviderCopyData(dataProviderRef);
CFMutableDataRef mutableDataRef = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, dataRef);
// const UInt8 * constBytesPtr = CFDataGetBytePtr(dataRef);
UInt8 * bytesPtr = CFDataGetMutableBytePtr(mutableDataRef);
NSUInteger length = CFDataGetLength(mutableDataRef);
// 一个像素由四个字节byte组成
for (int i = 0; i < length; i += 4) {
UInt8 red = bytesPtr[I];
UInt8 green = bytesPtr[i + 1];
UInt8 blue = bytesPtr[i + 2];
UInt8 average = (red + green + blue) / 3;
bytesPtr[i] = average;
bytesPtr[i + 1] = average;
bytesPtr[i + 2] = average;
}
CGContextRef imageContextRef = CGBitmapContextCreate(bytesPtr, width, height, bitsPerComponent, bytesPerRow, space, bitmapInfo);
CGImageRef finalImageRef = CGBitmapContextCreateImage(imageContextRef);
UIImage * finalImage = [UIImage imageWithCGImage:finalImageRef];
_finalImageView.image = finalImage;
// ⚠️这里为什么一定要用UIImageJPEGRepresentation才能成功,而UIImagePNGRepresentation会失败,生成的数据是正确的,但是相册相应的资源无法更新???
// 因为这个和renderedContentURL有关,图片的输出只能是JPEG格式,video只能是.mov格式输出
// 对于Live Photo,调用saveLivePhotoToOutput:options:completionHandler:方法保存和更新
BOOL isSuccess = [UIImageJPEGRepresentation(finalImage, 1) writeToURL:output.renderedContentURL atomically:NO];
if (isSuccess) {
NSLog(@"写入成功");
} else {
NSLog(@"写入失败");
}
CGImageRelease(finalImageRef);
CGContextRelease(imageContextRef);
CFRelease(mutableDataRef);
CFRelease(dataRef);
// 5、赋值更新
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 使用PHAssetChangeRequest
PHAssetChangeRequest * changeRequest = [PHAssetChangeRequest changeRequestForAsset: firstAsset];
changeRequest.contentEditingOutput = output;
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"编辑内容成功");
} else {
NSLog(@"编辑内容失败:%@", error);
}
}];
}];
}
}
结果如下:
这一过程涉及到了很多前面没有提到的类,分别如下:
- PHAdjustmentData
该类的初始化使用如下方式:- (instancetype)initWithFormatIdentifier:(NSString *)formatIdentifier formatVersion:(NSString *)formatVersion data:(NSData *)data;
,这三个参数分别对应三个只读属性,从其命名可以看出在这里面记录了每次修改的信息。
尤其是data,这里记录了该次的操作,比如设置了滤镜等等。 - PHContentEditingInputRequestOptions
该option主要用来判断从哪个版本操作,比较有用的是下面的方法:
@property (nonatomic, copy) BOOL (^canHandleAdjustmentData)(PHAdjustmentData *adjustmentData);
这是一个带返回值的block,如果该block返回YES,那么接下来操作的资源是原来的纯净的资源的最后一次操作,否则,会选择最原始的资源即没有进行过操作的做操作。这一切都取决于PHAdjustmentData。
- PHContentEditingInput
当前要操作的资源,这里记录了图片、视频和Live Photo的一些操作的必须信息,比如图片的地址属性fullSizeImageURL,通用属性创建时间creationDate等等。
主要是用来获取资源的一些比较隐秘的信息的。 - PHContentEditingOutput
最后的要更新的信息都在这个类里了,该类的初始化需要依靠PHContentEditingInput对象,其中的adjustmentData用来标记这次的操作;而属性renderedContentURL是获取进行资源存储地址用的,对于图片和视频是必须调用这一步去存储资源到零时文件中的,而对于Live Photo,需要调用saveLivePhotoToOutput:options:completionHandler:
去存储。
需要注意的是,使用renderedContentURL方法,会将所有的图片都存储为jpeg格式的,所以需要使用UIImageJPEGRepresentation(finalImage, 1)方法来写入;会将所有的视频修改为.mov格式。
另外,在该方法中,还使用了一些额外的框架,来操作图片的每个点,获取每个像素的数据,生成图片等。
- 一键清除操作
这个很简单,只要我有了PHAsset,然后就可以通过PHAssetChangeRequest来调用方法revertAssetContentToOriginal
清除即可。
- (IBAction)toResetAsset:(id)sender {
PHFetchResult * result = [PHAsset fetchAssetsWithLocalIdentifiers:@[@"421C93B7-E05A-41FF-9983-02C1B8B41902/L0/001"] options:nil];
if (result.count == 0) {
return;
}
PHAsset * firstAsset = result.firstObject;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest * request = [PHAssetChangeRequest changeRequestForAsset:firstAsset];
[request revertAssetContentToOriginal];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"还原成功");
} else {
NSLog(@"还原失败");
}
}];
}
上面提到的增删改查都是从资源自身层面上去处理的,后面会发现在相册等层面也可以做这些处理,具体的下一节再写。
至此,关于单个资源的增删改查基本完毕,后面碰到这里漏了的再做补充!