如何使用 CloudKit API 将 PHLivePhoto 保存到 iCloud
Posted
技术标签:
【中文标题】如何使用 CloudKit API 将 PHLivePhoto 保存到 iCloud【英文标题】:How can I save an PHLivePhoto to iCloud using CloudKit API 【发布时间】:2016-06-29 17:11:18 【问题描述】:现在我正在使用以下代码。但是发生了一些奇怪的事情。
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];
然后使用CKModifyRecordsOperation
将该资产保存到iCloud,然后取回。
NSData *data = [NSData dataWithContentsOfURL: fetchedAsset.fileURL];
PHLivePhoto *thePhoto = [NSKeyedUnarchiver unarchiveObjectWithData: data];
PHLivePhotoView *photoView = ......;
photoView.livePhoto = thePhoto;
大部分都可以,除了当photoView停止播放时,photoView的图像就消失了。如果我再次长按它,它会正常播放。
为什么会这样?
【问题讨论】:
也许可以尝试保存个人PHAssetResource
s?
如何将 PHAssetResources 保存到 iCloud?有什么建议吗?谢谢。
我发现 NSKeyedArchiver 和 NSKeyedUnarchiver 应该为这个错误负责。我正在尝试在没有它们的情况下将 PHLivePhoto 写入 url。
【参考方案1】:
NSKeyedArchiver
似乎没有按预期保存实时照片。作为解决方案之一,您应该拆除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];
];
第二部分(从 iCloud 检索)有一个小“技巧”——您应该确保图像和视频都具有包含在元数据中的相同资产 ID,否则 ios 将不知道这两个部分(视频和图像)属于到一个复合资产 - 实时照片 - 并且将无法将它们组装成单个 正确 PHLivePhoto
对象(但是,在这种情况下,您很可能将得到 PHLivePhoto
,但它会被构造为静态照片,没有动画)。
这里最简单的方法是从视频资产中提取资产 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
];
根据 Apple 的文档,可能会多次调用带有实时照片的生成块(有关详细信息,请参阅 PHLivePhoto.h
):
结果处理程序将被多次调用以交付内容越来越多的新 PHLivePhoto 实例。
另外,请记住,您应该添加所有必要的检查(实际上有很多)和错误处理程序等。
【讨论】:
以上是关于如何使用 CloudKit API 将 PHLivePhoto 保存到 iCloud的主要内容,如果未能解决你的问题,请参考以下文章
在 Google Apps 脚本中使用 CloudKit Web 服务 API 查询 CloudKit 公共数据库时的 AUTHENTICATION_FAILED
如何使用 Cloudkit 将字典(或任何其他复杂结构)保存到 iCloud 中?