You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
588 lines
22 KiB
588 lines
22 KiB
#import "ImagePickerManager.h"
|
|
#import "ImagePickerUtils.h"
|
|
#import <React/RCTConvert.h>
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#import <Photos/Photos.h>
|
|
#import <PhotosUI/PhotosUI.h>
|
|
|
|
@import MobileCoreServices;
|
|
|
|
@interface ImagePickerManager ()
|
|
|
|
@property (nonatomic, strong) RCTResponseSenderBlock callback;
|
|
@property (nonatomic, copy) NSDictionary *options;
|
|
|
|
@end
|
|
|
|
@interface ImagePickerManager (UIImagePickerControllerDelegate) <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
|
|
@end
|
|
|
|
@interface ImagePickerManager (UIAdaptivePresentationControllerDelegate) <UIAdaptivePresentationControllerDelegate>
|
|
@end
|
|
|
|
#if __has_include(<PhotosUI/PHPicker.h>)
|
|
@interface ImagePickerManager (PHPickerViewControllerDelegate) <PHPickerViewControllerDelegate>
|
|
@end
|
|
#endif
|
|
|
|
@implementation ImagePickerManager
|
|
|
|
NSString *errCameraUnavailable = @"camera_unavailable";
|
|
NSString *errPermission = @"permission";
|
|
NSString *errOthers = @"others";
|
|
RNImagePickerTarget target;
|
|
|
|
BOOL photoSelected = NO;
|
|
|
|
RCT_EXPORT_MODULE(ImagePicker)
|
|
|
|
RCT_EXPORT_METHOD(launchCamera:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
target = camera;
|
|
photoSelected = NO;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self launchImagePicker:options callback:callback];
|
|
});
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(launchImageLibrary:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
|
|
{
|
|
target = library;
|
|
photoSelected = NO;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self launchImagePicker:options callback:callback];
|
|
});
|
|
}
|
|
|
|
// We won't compile this code when we build for the old architecture.
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
|
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
{
|
|
return std::make_shared<facebook::react::NativeImagePickerSpecJSI>(params);
|
|
}
|
|
#endif
|
|
|
|
- (void)launchImagePicker:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback
|
|
{
|
|
self.callback = callback;
|
|
|
|
if (target == camera && [ImagePickerUtils isSimulator]) {
|
|
self.callback(@[@{@"errorCode": errCameraUnavailable}]);
|
|
return;
|
|
}
|
|
|
|
self.options = options;
|
|
|
|
#if __has_include(<PhotosUI/PHPicker.h>)
|
|
if (@available(iOS 14, *)) {
|
|
if (target == library) {
|
|
PHPickerConfiguration *configuration = [ImagePickerUtils makeConfigurationFromOptions:options target:target];
|
|
PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:configuration];
|
|
picker.delegate = self;
|
|
picker.modalPresentationStyle = [RCTConvert UIModalPresentationStyle:options[@"presentationStyle"]];
|
|
picker.presentationController.delegate = self;
|
|
|
|
if([self.options[@"includeExtra"] boolValue]) {
|
|
|
|
[self checkPhotosPermissions:^(BOOL granted) {
|
|
if (!granted) {
|
|
self.callback(@[@{@"errorCode": errPermission}]);
|
|
return;
|
|
}
|
|
[self showPickerViewController:picker];
|
|
}];
|
|
} else {
|
|
[self showPickerViewController:picker];
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
|
|
[ImagePickerUtils setupPickerFromOptions:picker options:self.options target:target];
|
|
picker.delegate = self;
|
|
|
|
if([self.options[@"includeExtra"] boolValue]) {
|
|
[self checkPhotosPermissions:^(BOOL granted) {
|
|
if (!granted) {
|
|
self.callback(@[@{@"errorCode": errPermission}]);
|
|
return;
|
|
}
|
|
[self showPickerViewController:picker];
|
|
}];
|
|
} else {
|
|
[self showPickerViewController:picker];
|
|
}
|
|
}
|
|
|
|
- (void) showPickerViewController:(UIViewController *)picker
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
UIViewController *root = RCTPresentedViewController();
|
|
[root presentViewController:picker animated:YES completion:nil];
|
|
});
|
|
}
|
|
|
|
#pragma mark - Helpers
|
|
|
|
NSData* extractImageData(UIImage* image){
|
|
CFMutableDataRef imageData = CFDataCreateMutable(NULL, 0);
|
|
CGImageDestinationRef destination = CGImageDestinationCreateWithData(imageData, kUTTypeJPEG, 1, NULL);
|
|
|
|
CFStringRef orientationKey[1];
|
|
CFTypeRef orientationValue[1];
|
|
CGImagePropertyOrientation CGOrientation = CGImagePropertyOrientationForUIImageOrientation(image.imageOrientation);
|
|
|
|
orientationKey[0] = kCGImagePropertyOrientation;
|
|
orientationValue[0] = CFNumberCreate(NULL, kCFNumberIntType, &CGOrientation);
|
|
|
|
CFDictionaryRef imageProps = CFDictionaryCreate( NULL, (const void **)orientationKey, (const void **)orientationValue, 1,
|
|
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
|
|
CGImageDestinationAddImage(destination, image.CGImage, imageProps);
|
|
|
|
CGImageDestinationFinalize(destination);
|
|
|
|
CFRelease(destination);
|
|
CFRelease(orientationValue[0]);
|
|
CFRelease(imageProps);
|
|
return (__bridge NSData *)imageData;
|
|
}
|
|
|
|
|
|
|
|
-(NSMutableDictionary *)mapImageToAsset:(UIImage *)image data:(NSData *)data phAsset:(PHAsset * _Nullable)phAsset {
|
|
NSString *fileType = [ImagePickerUtils getFileType:data];
|
|
if (target == camera) {
|
|
if ([self.options[@"saveToPhotos"] boolValue]) {
|
|
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
|
|
}
|
|
data = extractImageData(image);
|
|
}
|
|
|
|
UIImage* newImage = image;
|
|
if (![fileType isEqualToString:@"gif"]) {
|
|
newImage = [ImagePickerUtils resizeImage:image
|
|
maxWidth:[self.options[@"maxWidth"] floatValue]
|
|
maxHeight:[self.options[@"maxHeight"] floatValue]];
|
|
}
|
|
|
|
float quality = [self.options[@"quality"] floatValue];
|
|
if (![image isEqual:newImage] || (quality >= 0 && quality < 1)) {
|
|
if ([fileType isEqualToString:@"jpg"]) {
|
|
data = UIImageJPEGRepresentation(newImage, quality);
|
|
} else if ([fileType isEqualToString:@"png"]) {
|
|
data = UIImagePNGRepresentation(newImage);
|
|
}
|
|
}
|
|
|
|
NSMutableDictionary *asset = [[NSMutableDictionary alloc] init];
|
|
asset[@"type"] = [@"image/" stringByAppendingString:fileType];
|
|
|
|
NSString *fileName = [self getImageFileName:fileType];
|
|
NSString *path = [[NSTemporaryDirectory() stringByStandardizingPath] stringByAppendingPathComponent:fileName];
|
|
[data writeToFile:path atomically:YES];
|
|
|
|
if ([self.options[@"includeBase64"] boolValue]) {
|
|
asset[@"base64"] = [data base64EncodedStringWithOptions:0];
|
|
}
|
|
|
|
NSURL *fileURL = [NSURL fileURLWithPath:path];
|
|
asset[@"uri"] = [fileURL absoluteString];
|
|
|
|
NSNumber *fileSizeValue = nil;
|
|
NSError *fileSizeError = nil;
|
|
[fileURL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:&fileSizeError];
|
|
if (fileSizeValue){
|
|
asset[@"fileSize"] = fileSizeValue;
|
|
}
|
|
|
|
asset[@"fileName"] = fileName;
|
|
asset[@"width"] = @(newImage.size.width);
|
|
asset[@"height"] = @(newImage.size.height);
|
|
|
|
if(phAsset){
|
|
asset[@"timestamp"] = [self getDateTimeInUTC:phAsset.creationDate];
|
|
asset[@"id"] = phAsset.localIdentifier;
|
|
// Add more extra data here ...
|
|
}
|
|
|
|
return asset;
|
|
}
|
|
|
|
CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIImageOrientation uiOrientation) {
|
|
//code from here: https://developer.apple.com/documentation/imageio/cgimagepropertyorientation?language=objc
|
|
switch (uiOrientation) {
|
|
case UIImageOrientationUp: return kCGImagePropertyOrientationUp;
|
|
case UIImageOrientationDown: return kCGImagePropertyOrientationDown;
|
|
case UIImageOrientationLeft: return kCGImagePropertyOrientationLeft;
|
|
case UIImageOrientationRight: return kCGImagePropertyOrientationRight;
|
|
case UIImageOrientationUpMirrored: return kCGImagePropertyOrientationUpMirrored;
|
|
case UIImageOrientationDownMirrored: return kCGImagePropertyOrientationDownMirrored;
|
|
case UIImageOrientationLeftMirrored: return kCGImagePropertyOrientationLeftMirrored;
|
|
case UIImageOrientationRightMirrored: return kCGImagePropertyOrientationRightMirrored;
|
|
}
|
|
}
|
|
|
|
-(NSMutableDictionary *)mapVideoToAsset:(NSURL *)url phAsset:(PHAsset * _Nullable)phAsset error:(NSError **)error {
|
|
NSString *fileName = [url lastPathComponent];
|
|
NSString *path = [[NSTemporaryDirectory() stringByStandardizingPath] stringByAppendingPathComponent:fileName];
|
|
NSURL *videoDestinationURL = [NSURL fileURLWithPath:path];
|
|
|
|
if ((target == camera) && [self.options[@"saveToPhotos"] boolValue]) {
|
|
UISaveVideoAtPathToSavedPhotosAlbum(url.path, nil, nil, nil);
|
|
}
|
|
|
|
if (![url.URLByResolvingSymlinksInPath.path isEqualToString:videoDestinationURL.URLByResolvingSymlinksInPath.path]) {
|
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
|
|
// Delete file if it already exists
|
|
if ([fileManager fileExistsAtPath:videoDestinationURL.path]) {
|
|
[fileManager removeItemAtURL:videoDestinationURL error:nil];
|
|
}
|
|
|
|
if (url) { // Protect against reported crash
|
|
|
|
// If we have write access to the source file, move it. Otherwise use copy.
|
|
if ([fileManager isWritableFileAtPath:[url path]]) {
|
|
[fileManager moveItemAtURL:url toURL:videoDestinationURL error:error];
|
|
} else {
|
|
[fileManager copyItemAtURL:url toURL:videoDestinationURL error:error];
|
|
}
|
|
|
|
if (error && *error) {
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
|
|
|
|
if([self.options[@"formatAsMp4"] boolValue]) {
|
|
NSURL *parentURL = [videoDestinationURL URLByDeletingLastPathComponent];
|
|
NSString *path = [[parentURL.path stringByAppendingString:@"/"] stringByAppendingString:[[NSUUID UUID] UUIDString]];
|
|
path = [path stringByAppendingString:@".mp4"];
|
|
NSURL *outputURL = [NSURL fileURLWithPath:path];
|
|
|
|
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
|
|
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:videoDestinationURL options:nil];
|
|
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetPassthrough];
|
|
|
|
exportSession.outputURL = outputURL;
|
|
exportSession.outputFileType = AVFileTypeMPEG4;
|
|
exportSession.shouldOptimizeForNetworkUse = YES;
|
|
|
|
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
|
|
|
|
[exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
|
|
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
|
|
CGSize dimentions = [ImagePickerUtils getVideoDimensionsFromUrl:outputURL];
|
|
response[@"fileName"] = [outputURL lastPathComponent];
|
|
response[@"duration"] = [NSNumber numberWithDouble:CMTimeGetSeconds([AVAsset assetWithURL:outputURL].duration)];
|
|
response[@"uri"] = outputURL.absoluteString;
|
|
response[@"type"] = [ImagePickerUtils getFileTypeFromUrl:outputURL];
|
|
response[@"fileSize"] = [ImagePickerUtils getFileSizeFromUrl:outputURL];
|
|
response[@"width"] = @(dimentions.width);
|
|
response[@"height"] = @(dimentions.height);
|
|
|
|
dispatch_semaphore_signal(sem);
|
|
} else if (exportSession.status == AVAssetExportSessionStatusFailed || exportSession.status == AVAssetExportSessionStatusCancelled) {
|
|
dispatch_semaphore_signal(sem);
|
|
}
|
|
}];
|
|
|
|
|
|
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
|
} else {
|
|
CGSize dimentions = [ImagePickerUtils getVideoDimensionsFromUrl:videoDestinationURL];
|
|
response[@"fileName"] = fileName;
|
|
response[@"duration"] = [NSNumber numberWithDouble:CMTimeGetSeconds([AVAsset assetWithURL:videoDestinationURL].duration)];
|
|
response[@"uri"] = videoDestinationURL.absoluteString;
|
|
response[@"type"] = [ImagePickerUtils getFileTypeFromUrl:videoDestinationURL];
|
|
response[@"fileSize"] = [ImagePickerUtils getFileSizeFromUrl:videoDestinationURL];
|
|
response[@"width"] = @(dimentions.width);
|
|
response[@"height"] = @(dimentions.height);
|
|
|
|
if(phAsset){
|
|
response[@"timestamp"] = [self getDateTimeInUTC:phAsset.creationDate];
|
|
response[@"id"] = phAsset.localIdentifier;
|
|
// Add more extra data here ...
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
- (NSString *) getDateTimeInUTC:(NSDate *)date {
|
|
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
|
|
return [formatter stringFromDate:date];
|
|
}
|
|
|
|
- (void)checkCameraPermissions:(void(^)(BOOL granted))callback
|
|
{
|
|
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
|
if (status == AVAuthorizationStatusAuthorized) {
|
|
callback(YES);
|
|
return;
|
|
}
|
|
else if (status == AVAuthorizationStatusNotDetermined){
|
|
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
|
|
callback(granted);
|
|
return;
|
|
}];
|
|
}
|
|
else {
|
|
callback(NO);
|
|
}
|
|
}
|
|
|
|
- (void)checkPhotosPermissions:(void(^)(BOOL granted))callback
|
|
{
|
|
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
|
|
if (status == PHAuthorizationStatusAuthorized) {
|
|
callback(YES);
|
|
return;
|
|
} else if (status == PHAuthorizationStatusNotDetermined) {
|
|
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
|
|
if (status == PHAuthorizationStatusAuthorized) {
|
|
callback(YES);
|
|
return;
|
|
}
|
|
else {
|
|
callback(NO);
|
|
return;
|
|
}
|
|
}];
|
|
}
|
|
else {
|
|
callback(NO);
|
|
}
|
|
}
|
|
|
|
// Both camera and photo write permission is required to take picture/video and store it to public photos
|
|
- (void)checkCameraAndPhotoPermission:(void(^)(BOOL granted))callback
|
|
{
|
|
[self checkCameraPermissions:^(BOOL cameraGranted) {
|
|
if (!cameraGranted) {
|
|
callback(NO);
|
|
return;
|
|
}
|
|
|
|
[self checkPhotosPermissions:^(BOOL photoGranted) {
|
|
if (!photoGranted) {
|
|
callback(NO);
|
|
return;
|
|
}
|
|
callback(YES);
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (void)checkPermission:(void(^)(BOOL granted)) callback
|
|
{
|
|
void (^permissionBlock)(BOOL) = ^(BOOL permissionGranted) {
|
|
if (!permissionGranted) {
|
|
callback(NO);
|
|
return;
|
|
}
|
|
callback(YES);
|
|
};
|
|
|
|
if (target == camera && [self.options[@"saveToPhotos"] boolValue]) {
|
|
[self checkCameraAndPhotoPermission:permissionBlock];
|
|
}
|
|
else if (target == camera) {
|
|
[self checkCameraPermissions:permissionBlock];
|
|
}
|
|
else {
|
|
if (@available(iOS 11.0, *)) {
|
|
callback(YES);
|
|
}
|
|
else {
|
|
[self checkPhotosPermissions:permissionBlock];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSString *)getImageFileName:(NSString *)fileType
|
|
{
|
|
NSString *fileName = [[NSUUID UUID] UUIDString];
|
|
fileName = [fileName stringByAppendingString:@"."];
|
|
return [fileName stringByAppendingString:fileType];
|
|
}
|
|
|
|
+ (UIImage *)getUIImageFromInfo:(NSDictionary *)info
|
|
{
|
|
UIImage *image = info[UIImagePickerControllerEditedImage];
|
|
if (!image) {
|
|
image = info[UIImagePickerControllerOriginalImage];
|
|
}
|
|
return image;
|
|
}
|
|
|
|
+ (NSURL *)getNSURLFromInfo:(NSDictionary *)info {
|
|
if (@available(iOS 11.0, *)) {
|
|
return info[UIImagePickerControllerImageURL];
|
|
}
|
|
else {
|
|
return info[UIImagePickerControllerReferenceURL];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation ImagePickerManager (UIImagePickerControllerDelegate)
|
|
|
|
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
|
|
{
|
|
dispatch_block_t dismissCompletionBlock = ^{
|
|
NSMutableArray<NSDictionary *> *assets = [[NSMutableArray alloc] initWithCapacity:1];
|
|
PHAsset *asset = nil;
|
|
|
|
if (photoSelected == YES) {
|
|
return;
|
|
}
|
|
photoSelected = YES;
|
|
|
|
// If include extra, we fetch the PHAsset, this required library permissions
|
|
if([self.options[@"includeExtra"] boolValue]) {
|
|
asset = [ImagePickerUtils fetchPHAssetOnIOS13:info];
|
|
}
|
|
|
|
if ([info[UIImagePickerControllerMediaType] isEqualToString:(NSString *) kUTTypeImage]) {
|
|
UIImage *image = [ImagePickerManager getUIImageFromInfo:info];
|
|
|
|
[assets addObject:[self mapImageToAsset:image data:[NSData dataWithContentsOfURL:[ImagePickerManager getNSURLFromInfo:info]] phAsset:asset]];
|
|
} else {
|
|
NSError *error;
|
|
NSDictionary *videoAsset = [self mapVideoToAsset:info[UIImagePickerControllerMediaURL] phAsset:asset error:&error];
|
|
|
|
if (videoAsset == nil) {
|
|
NSString *errorMessage = error.localizedFailureReason;
|
|
if (errorMessage == nil) errorMessage = @"Video asset not found";
|
|
self.callback(@[@{@"errorCode": errOthers, @"errorMessage": errorMessage}]);
|
|
return;
|
|
}
|
|
[assets addObject:videoAsset];
|
|
}
|
|
|
|
NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
|
|
response[@"assets"] = assets;
|
|
self.callback(@[response]);
|
|
};
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[picker dismissViewControllerAnimated:YES completion:dismissCompletionBlock];
|
|
});
|
|
}
|
|
|
|
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[picker dismissViewControllerAnimated:YES completion:^{
|
|
self.callback(@[@{@"didCancel": @YES}]);
|
|
}];
|
|
});
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation ImagePickerManager (presentationControllerDidDismiss)
|
|
|
|
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
|
{
|
|
self.callback(@[@{@"didCancel": @YES}]);
|
|
}
|
|
|
|
@end
|
|
|
|
#if __has_include(<PhotosUI/PHPicker.h>)
|
|
@implementation ImagePickerManager (PHPickerViewControllerDelegate)
|
|
|
|
- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14))
|
|
{
|
|
[picker dismissViewControllerAnimated:YES completion:nil];
|
|
|
|
if (photoSelected == YES) {
|
|
return;
|
|
}
|
|
photoSelected = YES;
|
|
|
|
if (results.count == 0) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
self.callback(@[@{@"didCancel": @YES}]);
|
|
});
|
|
return;
|
|
}
|
|
|
|
dispatch_group_t completionGroup = dispatch_group_create();
|
|
NSMutableArray<NSDictionary *> *assets = [[NSMutableArray alloc] initWithCapacity:results.count];
|
|
for (int i = 0; i < results.count; i++) {
|
|
[assets addObject:(NSDictionary *)[NSNull null]];
|
|
}
|
|
|
|
[results enumerateObjectsUsingBlock:^(PHPickerResult *result, NSUInteger index, BOOL *stop) {
|
|
PHAsset *asset = nil;
|
|
NSItemProvider *provider = result.itemProvider;
|
|
|
|
// If include extra, we fetch the PHAsset, this required library permissions
|
|
if([self.options[@"includeExtra"] boolValue] && result.assetIdentifier != nil) {
|
|
PHFetchResult* fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[result.assetIdentifier] options:nil];
|
|
asset = fetchResult.firstObject;
|
|
}
|
|
|
|
dispatch_group_enter(completionGroup);
|
|
|
|
if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
|
|
NSString *identifier = provider.registeredTypeIdentifiers.firstObject;
|
|
// Matches both com.apple.live-photo-bundle and com.apple.private.live-photo-bundle
|
|
if ([identifier containsString:@"live-photo-bundle"]) {
|
|
// Handle live photos
|
|
identifier = @"public.jpeg";
|
|
}
|
|
|
|
[provider loadFileRepresentationForTypeIdentifier:identifier completionHandler:^(NSURL * _Nullable url, NSError * _Nullable error) {
|
|
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
|
|
UIImage *image = [[UIImage alloc] initWithData:data];
|
|
|
|
assets[index] = [self mapImageToAsset:image data:data phAsset:asset];
|
|
dispatch_group_leave(completionGroup);
|
|
}];
|
|
} else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeMovie]) {
|
|
[provider loadFileRepresentationForTypeIdentifier:(NSString *)kUTTypeMovie completionHandler:^(NSURL * _Nullable url, NSError * _Nullable error) {
|
|
NSDictionary *mappedAsset = [self mapVideoToAsset:url phAsset:asset error:nil];
|
|
if (nil != mappedAsset) {
|
|
assets[index] = mappedAsset;
|
|
}
|
|
dispatch_group_leave(completionGroup);
|
|
}];
|
|
} else {
|
|
// The provider didn't have an item matching photo or video (fails on M1 Mac Simulator)
|
|
dispatch_group_leave(completionGroup);
|
|
}
|
|
}];
|
|
|
|
dispatch_group_notify(completionGroup, dispatch_get_main_queue(), ^{
|
|
// mapVideoToAsset can fail and return nil, leaving asset NSNull.
|
|
for (NSDictionary *asset in assets) {
|
|
if ([asset isEqual:[NSNull null]]) {
|
|
self.callback(@[@{@"errorCode": errOthers}]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
|
|
[response setObject:assets forKey:@"assets"];
|
|
|
|
self.callback(@[response]);
|
|
});
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|