PHAsset的增删改查

作者: 大鹏鸟 | 来源:发表于2018-02-28 16:42 被阅读854次

    在上一部分简单的了解了一下资源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<NSFastEnumeration>)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<PHAsset *> * result = [PHAsset fetchAssetsWithLocalIdentifiers:identifierArr options:nil];
            [PHAssetChangeRequest deleteAssets:result];
        } completionHandler:^(BOOL success, NSError * _Nullable error) {
            if (success) {
                NSLog(@"删除成功!");
            } else {
                NSLog(@"删除失败:%@", error);
            }
        }];
    }
    
    这里的删除是可交互式的,如下图所示: 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<PHAsset *> * 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;即可。
      在操作修改内容之前,还是有必要先检查一下是否可以修改内容。
      对于编辑的相关步骤如下: 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<PHAsset *> * 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);
                    }
                }];
            }];
        }
    }
    

    结果如下:


    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<PHAsset *> * 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的增删改查

        本文链接:https://www.haomeiwen.com/subject/dbzpxftx.html