diff --git a/src/ios/CDVCamera.h b/src/ios/CDVCamera.h index 0eff7a9ce..99853629c 100644 --- a/src/ios/CDVCamera.h +++ b/src/ios/CDVCamera.h @@ -68,7 +68,7 @@ typedef NSUInteger CDVMediaType; API_AVAILABLE(ios(14)) @interface CDVGalleryPicker : NSObject -@property (strong) PHPickerViewController* pickerViewController; +@property (strong) UIViewController* pickerViewController; @property (strong) CDVPictureOptions* pictureOptions; @property (copy) NSString* callbackId; @@ -97,7 +97,8 @@ API_AVAILABLE(ios(14)) UINavigationControllerDelegate, UIPopoverControllerDelegate, CLLocationManagerDelegate, - PHPickerViewControllerDelegate> + PHPickerViewControllerDelegate, + UIDocumentPickerDelegate> {} @property (strong) CDVCameraPicker* pickerController; @property (strong) CDVGalleryPicker* galleryPicker; diff --git a/src/ios/CDVCamera.m b/src/ios/CDVCamera.m index aad3ed1ef..ad59b1a66 100644 --- a/src/ios/CDVCamera.m +++ b/src/ios/CDVCamera.m @@ -31,6 +31,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import #import #import +#import #ifndef __CORDOVA_4_0_0 #import @@ -218,7 +219,17 @@ - (void)takePicture:(CDVInvokedUrlCommand*)command CDVGalleryPicker* picker = [CDVGalleryPicker createFromPictureOptions:pictureOptions]; picker.callbackId = command.callbackId; weakSelf.galleryPicker = picker; - picker.pickerViewController.delegate = weakSelf; + + if ([picker.pickerViewController isKindOfClass:[PHPickerViewController class]]) { + PHPickerViewController* controller = (PHPickerViewController*) picker.pickerViewController; + controller.delegate = weakSelf; + } else if ([picker.pickerViewController isKindOfClass:[UIDocumentPickerViewController class]]) { + UIDocumentPickerViewController* controller = (UIDocumentPickerViewController*) picker.pickerViewController; + controller.delegate = weakSelf; + } else { + NSLog(@"FIA: no class matched"); + } + [weakSelf.viewController presentViewController:picker.pickerViewController animated:true completion:^{ weakSelf.hasPendingOperation = NO; @@ -459,56 +470,99 @@ - (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPic return data; } -- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)) { +- (void)documentPicker:(UIDocumentPickerViewController *)controller +didPickDocumentsAtURLs:(NSArray *)urls; { __weak CDVCamera* weakSelf = self; + if (urls.count > 0) { + NSURL *fileURL = [urls firstObject]; + + NSError *error; + NSData *data = [NSData dataWithContentsOfURL:fileURL options:NSDataReadingMappedIfSafe error:&error]; + + if (error) { + [self sendErrorResultWithMessage]; + return; + } + + // Create a UIImage from the data + UIImage *original = [[UIImage alloc] initWithData:data]; + [self handleImageFromPicker:original withData:data]; + } else { + [self sendErrorResultWithMessage]; + } +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller; { + [self sendErrorResultWithMessage]; +} + +- (void)sendErrorResultWithMessage { + CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No Image Selected"]; + [self.commandDelegate sendPluginResult:result callbackId:self.galleryPicker.callbackId]; + + // Reset any pending operation state + self.hasPendingOperation = NO; + self.galleryPicker = nil; +} + +- (void)handleImageFromPicker:(UIImage *)original withData:(NSData *)data { + + __weak CDVCamera* weakSelf = self; + + UIImage* image = [weakSelf conformImage:original toOptions:weakSelf.galleryPicker.pictureOptions]; + + NSData* processedImageData = nil; + switch (weakSelf.galleryPicker.pictureOptions.encodingType) { + case EncodingTypePNG: + processedImageData = UIImagePNGRepresentation(image); + break; + case EncodingTypeJPEG: + processedImageData = UIImageJPEGRepresentation(image, weakSelf.galleryPicker.pictureOptions.quality.floatValue / 100.0f); + break; + default: + NSAssert(NO, @"Missing implementation for encoding type (fallback to jpg)"); + processedImageData = UIImageJPEGRepresentation(image, weakSelf.galleryPicker.pictureOptions.quality.floatValue / 100.0f); + } + CGImageSourceRef processedImageSource = CGImageSourceCreateWithData((__bridge CFDataRef) processedImageData, NULL); + + NSDictionary* completeMetadata = [self convertImageMetadata:data]; + NSDictionary* metadata = [self filterImageMetadataFrom:completeMetadata]; + + NSMutableData* imageDataWithExif = [NSMutableData data]; + CFStringRef fileFormat = kuTTypeFromCDVEncodingType(weakSelf.galleryPicker.pictureOptions.encodingType); + + CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef) imageDataWithExif, fileFormat, 1, NULL); + CGImageDestinationAddImageFromSource(destinationImage, processedImageSource, 0, (__bridge CFDictionaryRef) metadata); + CGImageDestinationFinalize(destinationImage); + + CFRelease(processedImageSource); + CFRelease(destinationImage); + + NSString* extension = ExtensionFromCDVEncodingType(weakSelf.galleryPicker.pictureOptions.encodingType); + NSString* filePath = [self tempFilePath:extension]; + NSError* err = nil; + + CDVPluginResult* result = nil; + if (![imageDataWithExif writeToFile:filePath options:NSAtomicWrite error:&err]) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]]; + } + + [weakSelf.commandDelegate sendPluginResult:result callbackId:weakSelf.galleryPicker.callbackId]; + weakSelf.hasPendingOperation = NO; + weakSelf.galleryPicker = nil; +} + +- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)) { + + [picker dismissViewControllerAnimated:YES completion:^{ if (results.count > 0 && [results.firstObject.itemProvider hasItemConformingToTypeIdentifier:(NSString*)kUTTypeImage]) { [results.firstObject.itemProvider loadDataRepresentationForTypeIdentifier:(NSString*)kUTTypeImage completionHandler:^(NSData* data, NSError* error) { UIImage* original = [[UIImage alloc] initWithData:data]; - UIImage* image = [weakSelf conformImage:original toOptions:weakSelf.galleryPicker.pictureOptions]; - - NSData* processedImageData = nil; - switch (weakSelf.galleryPicker.pictureOptions.encodingType) { - case EncodingTypePNG: - processedImageData = UIImagePNGRepresentation(image); - break; - case EncodingTypeJPEG: - processedImageData = UIImageJPEGRepresentation(image, weakSelf.galleryPicker.pictureOptions.quality.floatValue / 100.0f); - break; - default: - NSAssert(NO, @"Missing implementation for encoding type (fallback to jpg)"); - processedImageData = UIImageJPEGRepresentation(image, weakSelf.galleryPicker.pictureOptions.quality.floatValue / 100.0f); - } - CGImageSourceRef processedImageSource = CGImageSourceCreateWithData((__bridge CFDataRef) processedImageData, NULL); - - NSDictionary* completeMetadata = [self convertImageMetadata:data]; - NSDictionary* metadata = [self filterImageMetadataFrom:completeMetadata]; - - NSMutableData* imageDataWithExif = [NSMutableData data]; - CFStringRef fileFormat = kuTTypeFromCDVEncodingType(weakSelf.galleryPicker.pictureOptions.encodingType); - - CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef) imageDataWithExif, fileFormat, 1, NULL); - CGImageDestinationAddImageFromSource(destinationImage, processedImageSource, 0, (__bridge CFDictionaryRef) metadata); - CGImageDestinationFinalize(destinationImage); - - CFRelease(processedImageSource); - CFRelease(destinationImage); - - NSString* extension = ExtensionFromCDVEncodingType(weakSelf.galleryPicker.pictureOptions.encodingType); - NSString* filePath = [self tempFilePath:extension]; - NSError* err = nil; - - CDVPluginResult* result = nil; - if (![imageDataWithExif writeToFile:filePath options:NSAtomicWrite error:&err]) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]]; - } else { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]]; - } - - [weakSelf.commandDelegate sendPluginResult:result callbackId:weakSelf.galleryPicker.callbackId]; - weakSelf.hasPendingOperation = NO; - weakSelf.galleryPicker = nil; + [self handleImageFromPicker:original withData:data]; }]; } else { CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No Image Selected"]; @@ -969,11 +1023,23 @@ + (instancetype) createFromPictureOptions:(CDVPictureOptions*)pictureOptions { CDVGalleryPicker* instance = [[CDVGalleryPicker alloc] init]; instance.pictureOptions = pictureOptions; - PHPickerConfiguration* singleImage = [[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary]; - singleImage.filter = PHPickerFilter.imagesFilter; - singleImage.selectionLimit = 1; - instance.pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:singleImage]; - instance.pickerViewController.presentationController.delegate = instance; + NSProcessInfo *processInfo = [NSProcessInfo processInfo]; + + if ((processInfo.isMacCatalystApp || processInfo.iOSAppOnMac)) { + // Running on macOS under Mac Catalyst, use UIDocumentPickerViewController for image selection + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:@[UTTypeImage]]; + documentPicker.allowsMultipleSelection = NO; + instance.pickerViewController = documentPicker; // Store as generic view controller + instance.pickerViewController.presentationController.delegate = instance; + } else { + // Use PHPickerViewController for image selection + PHPickerConfiguration* singleImage = [[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary]; + singleImage.filter = PHPickerFilter.imagesFilter; + singleImage.selectionLimit = 1; + PHPickerViewController *photoPicker = [[PHPickerViewController alloc] initWithConfiguration:singleImage]; + instance.pickerViewController = photoPicker; // Store as generic view controller + instance.pickerViewController.presentationController.delegate = instance; + } return instance; }