简体   繁体   English

如何使用CloudKit API将PHLivePhoto保存到iCloud

[英]How can I save an PHLivePhoto to iCloud using CloudKit API

Right now I am using the following code.But something weird is happening. 现在我正在使用以下代码。但是发生了一些奇怪的事情。

PHLivePhoto *livePhoto = .....;

NSString *fileName = [[NSProcessInfo processInfo] globallyUniqueString];
NSURL *url = [NSURL fileURLWithPath: NSTemporaryDirectory()];
url = [url URLByAppendingPathComponent: fileName];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject: livePhoto];
[data writeToURL: url atomically: YES];

CKAsset *asset = [[CKAsset alloc] initWithFileURL: url];

And then use CKModifyRecordsOperation to save that asset to iCloud,and then fetch back. 然后使用CKModifyRecordsOperation将资产保存到iCloud,然后取回。

NSData *data = [NSData dataWithContentsOfURL: fetchedAsset.fileURL];
PHLivePhoto *thePhoto = [NSKeyedUnarchiver unarchiveObjectWithData: data];
PHLivePhotoView *photoView = ......;
photoView.livePhoto = thePhoto;

Most of it works,except that when the photoView stops playing back,the photoView's image just disappears.If I long-touch it again, it plays back normally. 大多数功能都可以正常工作,只是当photoView停止播放时,photoView的图像才消失。如果我再次长按它,它可以正常播放。

Why is this happening? 为什么会这样呢?

It appears that NSKeyedArchiver does not save live photo as expected. 看来NSKeyedArchiver无法按预期保存实时照片。 As one of the solutions, you should dismantle PHLivePhoto and retrieve separately video and still image, then upload them to iCloud: 作为解决方案之一,您应该拆除PHLivePhoto并分别检索视频和静止图像,然后将它们上传到iCloud:

#import <Photos/Photos.h>
#import <CloudKit/CloudKit.h>
#import <MobileCoreServices/MobileCoreServices.h>

+ (void) disassembleLivePhoto:(nonnull PHLivePhoto*)livePhoto completion:(void (^__nonnull)(UIImage * _Nullable stillImage, AVURLAsset * _Nullable video, NSURL* _Nullable imageURL, NSURL* _Nullable videoURL))block
{
    NSArray<PHAssetResource*>* resources = [PHAssetResource assetResourcesForLivePhoto:livePhoto];

    __block PHAssetResource *resImage = nil, *resVideo = nil;
    NSString *fileName = [[NSUUID UUID] UUIDString];
    NSURL *urlMov = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[fileName stringByAppendingPathExtension:@"mov"]]];
    NSURL *urlImg = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[fileName stringByAppendingPathExtension:@"jpg"]]];

    [resources enumerateObjectsUsingBlock:^(PHAssetResource * _Nonnull res, NSUInteger idx, BOOL * _Nonnull stop) {
        if (res.type == PHAssetResourceTypePairedVideo){
            resVideo = res;
        } else if (res.type == PHAssetResourceTypePhoto){
            resImage = res;
        }
    }];

    [[PHAssetResourceManager defaultManager] writeDataForAssetResource:resVideo toFile:urlMov options:nil completionHandler:^(NSError * _Nullable error) {    
        [[PHAssetResourceManager defaultManager] writeDataForAssetResource:resImage toFile:urlImg options:nil completionHandler:^(NSError * _Nullable error) {
            block([UIImage imageWithData:[NSData dataWithContentsOfURL:urlImg]], [AVURLAsset assetWithURL:urlMov], urlImg, urlMov);
        }];
    }];
}

- (void) sendLivePhotoComponentsWithImageURL:(nonnull NSURL*)urlImage videoURL:(nonnull NSURL*)urlVideo completionBlock:(void(^ __nonnull)(CKRecord* __nullable recordLivePhoto))block
{    
    CKAsset *assetVideo = [[CKAsset alloc] initWithFileURL:urlVideo];
    CKAsset *assetImage = [[CKAsset alloc] initWithFileURL:urlImage];

    CKContainer *ckcContainer = [CKContainer defaultContainer];
    CKDatabase *ckdbPublic = [ckcContainer publicCloudDatabase];    // in this example I use public DB

    [ckcContainer fetchUserRecordIDWithCompletionHandler:^(CKRecordID * _Nullable ownerRecordID, NSError * _Nullable error) {
        CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"your_record_name_e.g._UUID" zoneID:ownerRecordID.zoneID]; 
        CKRecord *record = [[CKRecord alloc] initWithRecordType:@"your_record_type" recordID:recordID];
        record[@"your_video_asset_CK_key"] = assetVideo;
        record[@"your_image_asset_CK_key"] = assetImage;

        CKModifyRecordsOperation * op = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:@[record] recordIDsToDelete:nil];
        op.modifyRecordsCompletionBlock = ^void(NSArray<CKRecord *> * _Nullable savedRecords, NSArray<CKRecordID *> * _Nullable deletedRecordIDs, NSError * _Nullable operationError){
            block(savedRecords.firstObject);    // Done.
        }; 

        op.qualityOfService = NSQualityOfServiceUserInitiated;

        [ckdbPublic addOperation:op];
    }];
}

Second part (retrieval from iCloud) has a little 'trick' - you should ensure that both image and video have the same asset ID contained in metadata, otherwise iOS won't know that these two pieces (video and image) belong to one compound asset – live photo - and will fail to assemble them into single correct PHLivePhoto object (in this case, however, most probably you will get PHLivePhoto , but it will be constructed as a still photo, without animation). 第二部分(从iCloud检索)有一点“技巧”-您应确保图像和视频的元数据中包含相同的资产ID,否则iOS将不知道这两部分(视频和图像)属于一个复合物资源-实时照片-将无法将它们组合成单个正确的 PHLivePhoto对象(但是,在这种情况下,很可能获得PHLivePhoto ,但它将被构造为静态图片,没有动画)。

The easiest way here is to extract asset ID from the video asset and then modify the image part by assigning it the same ID: 这里最简单的方法是从视频资产中提取资产ID,然后通过为其分配相同的ID来修改图像部分:

- (void) assembleLivePhotoWithCKRecord:(nonnull CKRecord*)record completion:(void (^__nullable)(PHLivePhoto* _Nullable livePhoto))block
{
    // Operational data
    CKAsset *assetVideo = record[@"your_video_asset_CK_key"];
    CKAsset *assetImage = record[@"your_image_asset_CK_key"];

    // Get video and prepare local URLs
    NSString *fileName = [[NSUUID UUID] UUIDString];
    NSString *pathVideo = [NSTemporaryDirectory() stringByAppendingPathComponent:[fileName stringByAppendingPathExtension:@"mov"]];
    NSString *pathImage = [NSTemporaryDirectory() stringByAppendingPathComponent:[fileName stringByAppendingPathExtension:@"jpg"]];
    NSURL *urlVideo = [NSURL fileURLWithPath:pathVideo];
    NSURL *urlImage = [NSURL fileURLWithPath:pathImage];

    NSData *dataVideo = [NSData dataWithContentsOfURL:assetVideo.fileURL];
    [[NSFileManager defaultManager] createFileAtPath:pathVideo contents:dataVideo attributes:nil];

    // Getting video asset ID from metadata
    NSString *metaID = nil;
    NSArray<AVMetadataItem*>* metadata = [[AVURLAsset assetWithURL:urlVideo] metadata];
    for (AVMetadataItem *md in metadata){
        if ([md.identifier containsString:@"com.apple.quicktime.content.identifier"]){
            metaID = (NSString*)(md.value);
            break;
        }
    }

    // Get image
    NSData *dataImage = [NSData dataWithContentsOfURL:assetImage.fileURL];
    UIImage *image = [UIImage imageWithData:dataImage];
    CGImageRef ref = [image CGImage];

    // Update image's metadata to make it conform video metadata
    NSDictionary *imgMetadata = @{@"{MakerApple}": @{@"17": metaID}};
    NSMutableData *imageData = [NSMutableData new];
    CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)imageData, kUTTypeJPEG, 1, nil);
    CGImageDestinationAddImage(dest, ref, (CFDictionaryRef)imgMetadata);
    CGImageDestinationFinalize(dest);

    [imageData writeToFile:pathImage atomically:YES];


    [PHLivePhoto requestLivePhotoWithResourceFileURLs:@[urlImage, urlVideo] placeholderImage:nil targetSize:CGSizeZero contentMode:PHImageContentModeAspectFit resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nonnull info) {
        block(livePhoto);       // NOTE: this block may be called several times
    }];
}

The resulting block with the live photo may be called several times as per Apple's documentation (see PHLivePhoto.h for more info): 根据Apple的文档,可能会多次调用带有实时照片的结果块(有关更多信息,请参见PHLivePhoto.h ):

The result handler will be called multiple times to deliver new PHLivePhoto instances with increasingly more content. 将多次调用结果处理程序,以交付内容越来越多的新PHLivePhoto实例。

Also, please keep in mind that you should add all necessary checks (there is quite a bit of them, actually) and handlers for errors etc. 另外,请记住,您应该添加所有必要的检查(实际上有很多检查)和错误处理程序等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM