PHAsset的增删改查

在上一部分简单的了解了一下资源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)assets;删除,这里的参数是包含PHAsset对象的遵循NSFastEnumeration协议的对象。
    除此之外,更安全的操作是应先使用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);
        }
    }];
}

这里的删除是可交互式的,如下图所示:
PHAsset的增删改查_第1张图片
image.png
  • 更新图片和视频属性
    名义上是更新,但是也只能更新四个内容:创建日期、拍照地点、是否收藏、是否隐藏。
    同样的,在做这些属性修改之前,应先做一次判断最为合适。
    这里以修改是否已收藏属性为例。
- (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;即可。
    在操作修改内容之前,还是有必要先检查一下是否可以修改内容。
    对于编辑的相关步骤如下:
    PHAsset的增删改查_第2张图片
    image.png

    简单来说就四步:
    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);
                }
            }];
        }];
    }
}

结果如下:


PHAsset的增删改查_第3张图片
image.png

这一过程涉及到了很多前面没有提到的类,分别如下:

  • 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(@"还原失败");
        }
    }];
}

上面提到的增删改查都是从资源自身层面上去处理的,后面会发现在相册等层面也可以做这些处理,具体的下一节再写。

至此,关于单个资源的增删改查基本完毕,后面碰到这里漏了的再做补充!

你可能感兴趣的:(PHAsset的增删改查)