blob: c94b5ae3ee8daa96bac7029adf8a49606d41a051 [file]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import "CDVCamera.h"
#import "CDVJpegHeaderWriter.h"
#import "UIImage+CropScaleOrientation.h"
#import <ImageIO/CGImageProperties.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <AVFoundation/AVFoundation.h>
#import <ImageIO/CGImageSource.h>
#import <ImageIO/CGImageProperties.h>
#import <ImageIO/CGImageDestination.h>
#import <MobileCoreServices/UTCoreTypes.h>
#import <objc/message.h>
#import <Photos/Photos.h>
#ifndef __CORDOVA_4_0_0
#import <Cordova/NSData+Base64.h>
#endif
#define CDV_PHOTO_PREFIX @"cdv_photo_"
static NSString* toBase64(NSData* data)
{
SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
SEL s2 = NSSelectorFromString(@"base64EncodedString");
SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
if ([data respondsToSelector:s1]) {
NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s1];
return func(data, s1);
} else if ([data respondsToSelector:s2]) {
NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s2];
return func(data, s2);
} else if ([data respondsToSelector:s3]) {
NSString* (*func)(id, SEL, NSUInteger) = (void *)[data methodForSelector:s3];
return func(data, s3, 0);
} else {
return nil;
}
}
static NSString* MIME_PNG = @"image/png";
static NSString* MIME_JPEG = @"image/jpeg";
@implementation CDVPictureOptions
+ (instancetype)createFromTakePictureArguments:(CDVInvokedUrlCommand*)command
{
CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(UIImagePickerControllerSourceTypeCamera)] unsignedIntegerValue];
NSNumber* targetWidth = [command argumentAtIndex:3 withDefault:nil];
NSNumber* targetHeight = [command argumentAtIndex:4 withDefault:nil];
pictureOptions.targetSize = CGSizeMake(0, 0);
if ((targetWidth != nil) && (targetHeight != nil)) {
pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
}
pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
pictureOptions.cameraDirection = [[command argumentAtIndex:10 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
pictureOptions.usesGeolocation = NO;
return pictureOptions;
}
@end
@interface CDVCamera ()
// Redeclare CDVPlugin.hasPendingOperation as readwrite,
// so we can set it. Skips didReceiveMemoryWarning handling in
// CDVViewController when a plugin has a pending operation.
@property (readwrite, assign) BOOL hasPendingOperation;
@end
@implementation CDVCamera
@synthesize hasPendingOperation, cdvUIImagePickerController, locationManager;
/**
Reads the preference CameraUsesGeolocation from config.xml
to determine whether to include GPS location data in JPEG EXIF metadata.
@return YES if CameraUsesGeolocation is set to true, NO otherwise.
*/
- (BOOL)usesGeolocation
{
id useGeo = [self.commandDelegate.settings objectForKey:[@"CameraUsesGeolocation" lowercaseString]];
return [(NSNumber*)useGeo boolValue];
}
/**
Called by JS function navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions)
which will invoke the camera or photo picker to capture or select an image or video.
@param command A Cordova command whose arguments map to camera options:
- index 0 (quality): NSNumber (1–100). JPEG quality when encodingType is JPEG. Default: 50.
- index 1 (destinationType): NSNumber (DestinationType). File URI or Data URL. Default: File URI.
- index 2 (sourceType): NSNumber (UIImagePickerControllerSourceType). Camera or Photo Library. Default: Camera.
- index 3 (targetWidth): NSNumber (optional). Desired width for scaling/cropping.
- index 4 (targetHeight): NSNumber (optional). Desired height for scaling/cropping.
- index 5 (encodingType): NSNumber (EncodingType). JPEG or PNG. Default: JPEG.
- index 6 (mediaType): NSNumber (MediaType). Picture, Video, or All. Default: Picture.
- index 7 (allowsEditing): NSNumber(BOOL). Allow user to crop/edit. Default: NO.
- index 8 (correctOrientation): NSNumber(BOOL). Fix EXIF orientation. Default: NO.
- index 9 (saveToPhotoAlbum): NSNumber(BOOL). Save captured image to Photos. Default: NO.
- index 10 (cameraDirection): NSNumber (UIImagePickerControllerCameraDevice). Front/Rear. Default: Rear.
@discussion
This method validates hardware availability and permissions (camera or photo library),
then presents the appropriate UI (UIImagePickerController or PHPickerViewController on iOS 14+).
The result is returned via the Cordova callback.
*/
- (void)takePicture:(CDVInvokedUrlCommand*)command
{
self.hasPendingOperation = YES;
__weak CDVCamera* weakSelf = self;
[self.commandDelegate runInBackground:^{
CDVPictureOptions* pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
// Only for capturing JPEG images, get geolocation data to include in the EXIF header
pictureOptions.usesGeolocation = [weakSelf usesGeolocation];
pictureOptions.cropToSize = NO;
// The camera should be used to take a picture
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera) {
// Check if camera is available
if (![UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)pictureOptions.sourceType);
[weakSelf.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No camera available"]
callbackId:command.callbackId];
return;
}
// Validate the app has permission to access the camera
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
// Show an alert if not granted
if (!granted) {
[weakSelf presentPermissionDeniedAlertWithMessage:@"Access to the camera has been prohibited; please enable it in the Settings app to continue."
callbackId:command.callbackId];
} else {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
}
}];
// A photo should be picked from the photo library
} else {
// Use PHPickerViewController on iOS 14+
// Doesn't require permissions
if (@available(iOS 14, *)) {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
// On iOS < 14, use UIImagePickerController and request permissions
} else {
// Request permission
[weakSelf options:pictureOptions requestPhotoPermissions:^(BOOL granted) {
if (!granted) {
// Denied; show an alert
[weakSelf presentPermissionDeniedAlertWithMessage:@"Access to the camera roll has been prohibited; please enable it in the Settings to continue."
callbackId:command.callbackId];
} else {
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
}
}];
}
}
}];
}
/**
Presents a permission denial alert with OK and Settings actions.
@param message The alert message to show.
@param callbackId The Cordova callback identifier to send an error if needed.
*/
- (void)presentPermissionDeniedAlertWithMessage:(NSString*)message callbackId:(NSString*)callbackId
{
__weak CDVCamera *weakSelf = self;
// Perform UI creation and presentation on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSString *bundleDisplayName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:bundleDisplayName
message:NSLocalizedString(message, nil)
preferredStyle:UIAlertControllerStyleAlert];
// Ok button
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[weakSelf sendNoPermissionResult:callbackId];
}]];
// Button for open settings
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
// Open settings
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]
options:@{}
completionHandler:nil];
[weakSelf sendNoPermissionResult:callbackId];
}]];
[self.viewController presentViewController:alertController animated:YES completion:nil];
});
}
- (void)stop:(CDVInvokedUrlCommand*)command
{
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController* presentedViewController = self.viewController.presentedViewController;
if (presentedViewController == nil) {
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
return;
}
[presentedViewController dismissViewControllerAnimated:YES completion:^{
self.hasPendingOperation = NO;
self.cdvUIImagePickerController = nil;
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}];
});
}
- (void)sendNoPermissionResult:(NSString*)callbackId
{
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"]; // error callback expects string ATM
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
self.hasPendingOperation = NO;
self.cdvUIImagePickerController = nil;
}
/**
Presents the appropriate UI to capture or select media based on the provided options and OS version.
On iOS 14 and later, when the source type is PHOTOLIBRARY (or SAVEDPHOTOALBUM), this method presents
PHPickerViewController to select media without requiring Photos authorization. Otherwise, it falls back
to UIImagePickerController for camera usage or on older iOS versions.
Threading:
- Ensures presentation occurs on the main thread.
Behavior:
- Configures delegates and media types
- Updates `hasPendingOperation` to reflect plugin activity state.
@param callbackId The Cordova callback identifier used to deliver results back to JavaScript.
@param pictureOptions Parsed camera options (sourceType, mediaType, allowsEditing, etc.).
*/
- (void)showCameraPicker:(NSString*)callbackId withOptions:(CDVPictureOptions*)pictureOptions
{
// Use PHPickerViewController for photo library on iOS 14+
if (@available(iOS 14, *)) {
// sourceType is PHOTOLIBRARY
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypePhotoLibrary ||
// sourceType is SAVEDPHOTOALBUM (same as PHOTOLIBRARY)
pictureOptions.sourceType == UIImagePickerControllerSourceTypeSavedPhotosAlbum) {
[self showPHPicker:callbackId withOptions:pictureOptions];
return;
}
}
// Use UIImagePickerController for camera or as image picker for iOS older than 14
// UIImagePickerController must be created and presented on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
self.cdvUIImagePickerController = [CDVUIImagePickerController createFromPictureOptions:pictureOptions];
self.cdvUIImagePickerController.delegate = self;
self.cdvUIImagePickerController.callbackId = callbackId;
// we need to capture this state for memory warnings that dealloc this object
self.cdvUIImagePickerController.webView = self.webView;
self.cdvUIImagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.viewController presentViewController:self.cdvUIImagePickerController
animated:YES
completion:^{
self.hasPendingOperation = NO;
}];
});
}
// Since iOS 14, we can use PHPickerViewController to select images from the photo library
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 // Always true on XCode12+
- (void)showPHPicker:(NSString*)callbackId withOptions:(CDVPictureOptions*)pictureOptions API_AVAILABLE(ios(14))
{
// PHPicker must be created and presented on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
// Using [PHPickerConfiguration init] instead of
// [PHPickerConfiguration initWithPhotoLibrary:[PHPhotoLibrary sharedPhotoLibrary]]
// is more open and lets the picker return items that aren’t PHAssets, like cloud/shared providers,
// but will not return asset identifiers.
PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init];
// Configure filter based on media type
// Images
if (pictureOptions.mediaType == MediaTypePicture) {
config.filter = [PHPickerFilter imagesFilter];
// Videos
} else if (pictureOptions.mediaType == MediaTypeVideo) {
config.filter = [PHPickerFilter videosFilter];
// Images and videos
} else if (pictureOptions.mediaType == MediaTypeAll) {
config.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[
[PHPickerFilter imagesFilter],
[PHPickerFilter videosFilter]
]];
}
config.selectionLimit = 1;
// PHPickerConfigurationAssetRepresentationModeCurrent:
// A mode that uses the current representation to avoid transcoding, if possible.
// This means PHPicker tries to give you a representation already available without
// re‑encoding. That usually is the stored file on device (e.g., HEIC/JPEG),
// but if the asset is only in iCloud or already has a cached “current” rendition,
// you might get that cached representation instead of downloading the original.
// This plugin supports only JPEG and PNG currently and will convert the
// image later in processImage: to the requested format.
config.preferredAssetRepresentationMode = PHPickerConfigurationAssetRepresentationModeCurrent;
PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:config];
picker.delegate = self;
// Store callback ID and options in picker with objc_setAssociatedObject
// PHPickerViewController’s delegate method picker:didFinishPicking: only gives you back the picker instance
// and the results array. It doesn’t carry arbitrary context. By associating the callbackId and pictureOptions
// with the picker, you can retrieve them later inside the delegate method
objc_setAssociatedObject(picker, "callbackId", callbackId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(picker, "pictureOptions", pictureOptions, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self.viewController presentViewController:picker animated:YES completion:^{
self.hasPendingOperation = NO;
}];
});
}
// PHPickerViewControllerDelegate method
- (void)picker:(PHPickerViewController*)picker didFinishPicking:(NSArray<PHPickerResult*>*)results API_AVAILABLE(ios(14))
{
NSString *callbackId = objc_getAssociatedObject(picker, "callbackId");
CDVPictureOptions *pictureOptions = objc_getAssociatedObject(picker, "pictureOptions");
__weak CDVCamera* weakSelf = self;
[picker dismissViewControllerAnimated:YES completion:^{
if (results.count == 0) {
// User cancelled
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No Image Selected"];
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
weakSelf.hasPendingOperation = NO;
return;
}
PHPickerResult *pickerResult = results.firstObject;
// Check if it's a video
if ([pickerResult.itemProvider hasItemConformingToTypeIdentifier:UTTypeMovie.identifier]) {
// loadFileRepresentationForTypeIdentifier returns an url which will be gone after the completion handler returns,
// so we need to copy the video to a temporary location, which can be accessed later
[pickerResult.itemProvider loadFileRepresentationForTypeIdentifier:UTTypeMovie.identifier
completionHandler:^(NSURL * _Nullable url, NSError * _Nullable error) {
if (error) {
NSLog(@"CDVCamera: Failed to load video: %@", [error localizedDescription]);
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION
messageAsString:[NSString stringWithFormat:@"Failed to load video: %@", [error localizedDescription]]];
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
weakSelf.hasPendingOperation = NO;
return;
}
// Copy video to a temporary location, so it can be accessed after this completion handler returns
NSString* tempVideoPath = [weakSelf copyFileToTemp:[url path]];
// Send Cordova plugin result back
CDVPluginResult* result = nil;
if (tempVideoPath == nil) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION
messageAsString:@"Failed to copy video file to temporary location"];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:tempVideoPath];
}
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
weakSelf.hasPendingOperation = NO;
}];
// Handle image
} else if ([pickerResult.itemProvider hasItemConformingToTypeIdentifier:UTTypeImage.identifier]) {
// Load image data for the NSItemProvider
[pickerResult.itemProvider loadDataRepresentationForTypeIdentifier:UTTypeImage.identifier
completionHandler:^(NSData * _Nullable imageData, NSError * _Nullable error) {
if (error) {
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
messageAsString:[error localizedDescription]];
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
weakSelf.hasPendingOperation = NO;
return;
}
[weakSelf processPHPickerImage:[UIImage imageWithData:imageData]
metadata:[weakSelf convertImageMetadata:imageData]
callbackId:callbackId
options:pictureOptions];
}];
}
}];
}
/**
Prepares the image and metadata obtained from PHPickerImageViewController which will be processed in
resultForImage:. After that CDVPluginResult is returned.
*/
- (void)processPHPickerImage:(UIImage*)image
metadata:(NSDictionary*)metadata
callbackId:(NSString*)callbackId
options:(CDVPictureOptions*)options API_AVAILABLE(ios(14))
{
// To shrink the file size, only selected meta data like EXIF, TIFF, and GPS is used,
// which will be stored in self.metadata, which is set to the image in resultForImage:
// This code replicates the logic from processImage: for the UIImagePickerController
if (metadata.count > 0) {
self.metadata = [NSMutableDictionary dictionary];
NSDictionary *exif = metadata[(NSString *)kCGImagePropertyExifDictionary];
if (exif.count > 0) {
self.metadata[(NSString *)kCGImagePropertyExifDictionary] = [exif mutableCopy];
}
NSDictionary *tiff = metadata[(NSString *)kCGImagePropertyTIFFDictionary];
if (tiff.count > 0) {
self.metadata[(NSString *)kCGImagePropertyTIFFDictionary] = [tiff mutableCopy];
}
NSDictionary *gps = metadata[(NSString *)kCGImagePropertyGPSDictionary];
if (gps.count > 0) {
self.metadata[(NSString *)kCGImagePropertyGPSDictionary] = [gps mutableCopy];
}
}
// Mimic the info dictionary which would be created by UIImagePickerController
// Add image, which will be used in retrieveImage: to get the image and do processing
NSMutableDictionary *info = [@{UIImagePickerControllerOriginalImage : image} mutableCopy];
// Add metadata if available
if (metadata.count > 0) {
// This is not used anywhere and can be removed
info[UIImagePickerControllerMediaMetadata] = metadata;
}
// Return Cordova result to WebView
// Needed weakSelf for completion block
__weak CDVCamera* weakSelf = self;
// Process and return result
[self resultForImage:options info:info completion:^(CDVPluginResult* pluginResult) {
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.cdvUIImagePickerController = nil;
}];
}
#endif
// UINavigationControllerDelegate method
- (void)navigationController:(UINavigationController*)navigationController
willShowViewController:(UIViewController*)viewController
animated:(BOOL)animated
{
// Backward compatibility for iOS < 14
// Set title "Videos", when picking videos with the legacy UIImagePickerController
if([navigationController isKindOfClass:[UIImagePickerController class]]) {
UIImagePickerController* imagePickerController = (UIImagePickerController*)navigationController;
// Set title "Videos" when picking not images
if(![imagePickerController.mediaTypes containsObject:(NSString*)kUTTypeImage]) {
[viewController.navigationItem setTitle:NSLocalizedString(@"Videos", nil)];
}
}
}
- (NSString*)getMimeForEncoding:(CDVEncodingType)encoding
{
switch (encoding) {
case EncodingTypePNG: return MIME_PNG;
case EncodingTypeJPEG:
default:
return MIME_JPEG;
}
}
- (NSString*)formatAsDataURI:(NSData*)data withMIME:(NSString*)mime
{
NSString* base64 = toBase64(data);
if (base64 == nil) {
return nil;
}
return [NSString stringWithFormat:@"data:%@;base64,%@", mime, base64];
}
- (NSString*)processImageAsDataUri:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
{
NSString* mime = nil;
NSData* data = [self processImage:image info:info options:options outMime:&mime];
return [self formatAsDataURI:data withMIME:mime];
}
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
{
return [self processImage:image info:info options:options outMime:nil];
}
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options outMime:(NSString**)outMime
{
NSData* data = nil;
switch (options.encodingType) {
case EncodingTypePNG:
data = UIImagePNGRepresentation(image);
if (outMime != nil) *outMime = MIME_PNG;
break;
case EncodingTypeJPEG:
{
if (outMime != nil) *outMime = MIME_JPEG;
if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO) && (([options.quality integerValue] == 100) || (options.sourceType != UIImagePickerControllerSourceTypeCamera))){
// use image unedited as requested , don't resize
data = UIImageJPEGRepresentation(image, 1.0);
} else {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}
if (self.cdvUIImagePickerController.sourceType == UIImagePickerControllerSourceTypeCamera) {
// Include geolocation data in EXIF metadata if requested, this will
// be done in locationManager:didUpdateLocations:
// Note: This will be done only if UIImagePickerControllerMediaMetadata is available
if (options.usesGeolocation) {
// Get the metadata from the UIImagePickerController info dictionary
NSDictionary *mediaMetadata = info[UIImagePickerControllerMediaMetadata];
// Get location if mediaMetadata is set
if (mediaMetadata) {
// Store image data temporarly, so it can be returned after a location has gotten
self.tempImageDataForLocationManager = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSDictionary *exifDict = mediaMetadata[(NSString *)kCGImagePropertyExifDictionary];
if (exifDict.count > 0) {
self.metadata[(NSString *)kCGImagePropertyExifDictionary] = [exifDict mutableCopy];
}
[[self locationManager] requestWhenInUseAuthorization];
[[self locationManager] startUpdatingLocation];
}
// Don't return anything if options.usesGeolocation is set
// Data will be returned in locationManager:didUpdateLocations: or locationManager:didFailWithError:
// Note: If mediaMetadata is not set, this would also be set to nil, is this expected?
data = nil;
}
} else if (self.cdvUIImagePickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
PHAsset* asset = [info objectForKey:@"UIImagePickerControllerPHAsset"];
NSDictionary* controllerMetadata = [self getImageMetadataFromAsset:asset];
// Store image data temporarly, so it can be returned after a location has gotten
self.tempImageDataForLocationManager = data;
if (controllerMetadata.count > 0) {
self.metadata = [NSMutableDictionary dictionary];
NSDictionary *exif = controllerMetadata[(NSString *)kCGImagePropertyExifDictionary];
if (exif.count > 0) {
self.metadata[(NSString *)kCGImagePropertyExifDictionary] = [exif mutableCopy];
}
NSDictionary *tiff = controllerMetadata[(NSString *)kCGImagePropertyTIFFDictionary];
if (tiff.count > 0) {
self.metadata[(NSString *)kCGImagePropertyTIFFDictionary] = [tiff mutableCopy];
}
NSDictionary *gps = controllerMetadata[(NSString *)kCGImagePropertyGPSDictionary];
if (gps.count > 0) {
self.metadata[(NSString *)kCGImagePropertyGPSDictionary] = [gps mutableCopy];
}
}
}
}
break;
default:
break;
};
return data;
}
/* --------------------------------------------------------------
-- get the metadata of the image from a PHAsset
-------------------------------------------------------------- */
- (NSDictionary*)getImageMetadataFromAsset:(PHAsset*)asset
{
if(asset == nil) return nil;
// get photo info from this asset
__block NSDictionary *dict = nil;
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.synchronous = YES;
[[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset
options:imageRequestOptions
resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary *_Nullable info) {
// as this imageData is in NSData format so we need a method to convert this NSData into NSDictionary
dict = [self convertImageMetadata:imageData];
}];
return dict;
}
- (NSDictionary*)convertImageMetadata:(NSData*)imageData
{
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
if (imageSource) {
NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
if (imageProperties) {
NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
CFRelease(imageProperties);
CFRelease(imageSource);
NSLog(@"Metadata of selected image%@", metadata);// image metadata after converting NSData into NSDictionary
return metadata;
}
CFRelease(imageSource);
}
NSLog(@"Can't read image metadata");
return nil;
}
/**
Requests Photos library permissions when needed for picking media from the photo library.
This is only needed for iOS 13 and older when using UIImagePickerController for picking an image.
On iOS 14 and later, PHPickerViewController is used and does not need extra permissions.
@param options The picture options indicating the requested source type.
@param completion A block invoked with YES when access is authorized (or not required),
or NO when access is denied or restricted.
*/
- (void)options:(CDVPictureOptions*)options requestPhotoPermissions:(void (^)(BOOL auth))completion
{
// This is would be no good response
if (options.sourceType == UIImagePickerControllerSourceTypeCamera) {
completion(YES);
} else {
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
switch (status) {
case PHAuthorizationStatusAuthorized:
completion(YES);
break;
case PHAuthorizationStatusNotDetermined: {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus authorizationStatus) {
if (authorizationStatus == PHAuthorizationStatusAuthorized) {
completion(YES);
} else {
completion(NO);
}
}];
break;
}
default:
completion(NO);
break;
}
}
}
- (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)options
{
UIImage* pickedImage = nil;
// Get the picked image from info dictionary
if (options.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) {
pickedImage = [info objectForKey:UIImagePickerControllerEditedImage];
} else {
pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
}
// Process image according to options
UIImage *processedImage = pickedImage;
if (options.correctOrientation) {
processedImage = [processedImage imageCorrectedForCaptureOrientation];
}
// Scale with optional cropping
if (options.targetSize.width > 0 && options.targetSize.height > 0) {
// Scale and crop to target size
if (options.cropToSize) {
processedImage = [processedImage imageByScalingAndCroppingForSize:options.targetSize];
// Scale with no cropping
} else {
processedImage = [processedImage imageByScalingNotCroppingForSize:options.targetSize];
}
}
return processedImage;
}
- (void)resultForImage:(CDVPictureOptions*)pictureOptions
info:(NSDictionary*)info
completion:(void (^)(CDVPluginResult* res))completion
{
CDVPluginResult* result = nil;
UIImage* image = nil;
switch (pictureOptions.destinationType) {
case DestinationTypeDataUrl:
{
image = [self retrieveImage:info options:pictureOptions];
NSString* data = [self processImageAsDataUri:image info:info options:pictureOptions];
if (data) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: data];
}
}
break;
default: // DestinationTypeFileUri
{
image = [self retrieveImage:info options:pictureOptions];
NSData* imageData = [self processImage:image info:info options:pictureOptions];
if (imageData) {
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
NSData *imageDataToWrite = imageData;
// Copy custom choosen meta data stored in self.metadata to the image
if (self.metadata) {
NSMutableData *imageDataWithCustomMetaData = [NSMutableData data];
// Prepare source image
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)imageDataToWrite, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
// Prepare dest image
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithCustomMetaData, sourceType, 1, NULL);
// Copy source to dest with self.metadata
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage);
CFRelease(sourceImage);
CFRelease(destinationImage);
imageDataToWrite = imageDataWithCustomMetaData;
}
NSString* tempFilePath = [self tempFilePathForExtension:pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg"];
NSError* err = nil;
// Write image to temp path
if ([imageDataToWrite writeToFile:tempFilePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsString:[[NSURL fileURLWithPath:tempFilePath] absoluteString]];
// Write was not successful
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION
messageAsString:[err localizedDescription]];
}
} else if (pictureOptions.sourceType != UIImagePickerControllerSourceTypeCamera || !pictureOptions.usesGeolocation) {
// No need to save file if usesGeolocation is true since it will be saved after the location is tracked
NSString* extension = pictureOptions.encodingType == EncodingTypePNG? @"png" : @"jpg";
NSString* filePath = [self tempFilePathForExtension:extension];
NSError* err = nil;
// save file
if (![imageData writeToFile:filePath options:NSAtomicWrite error:&err]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION
messageAsString:[err localizedDescription]];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]];
}
}
}
}
break;
};
// Save the image to the photo album after capture
if (pictureOptions.saveToPhotoAlbum && image) {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
completion(result);
}
- (CDVPluginResult*)resultForVideo:(NSDictionary*)info
{
NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
// On iOS 13 the movie path becomes inaccessible, create and return a copy
if (@available(iOS 13, *)) {
moviePath = [self copyFileToTemp:[[info objectForKey:UIImagePickerControllerMediaURL] path]];
}
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
}
/**
Generates a unique temporary file path for a file extension.
The filename is prefixed with `cdv_photo_` and suffixed with the provided
file extension. A UNIX timestamp in milliseconds since 1970 is used to ensure
uniqueness between calls.
Threading: Safe to call from any thread. Uses NSTemporaryDirectory() and
does not perform any I/O; it only constructs a path string.
@param fileExtension The desired file extension without a leading dot
(for example, "jpg", "png", or the original video
extension like "mov").
@return An absolute path string within the app's temporary directory,
e.g. `/var/mobile/Containers/Data/Application/<UUID>/tmp/cdv_photo_<timestamp>.jpg`.
@discussion The returned path is not created on disk. Callers are responsible
for writing data to the path and handling any errors.
@note Only files whose names start with `cdv_photo_` are cleaned up by the
plugin's `cleanup:` method.
**/
- (NSString*)tempFilePathForExtension:(NSString*)fileExtension
{
// Return a unique file name like
// `/var/mobile/Containers/Data/Application/<UUID>/tmp/cdv_photo_<timestamp>.jpg`.
return [NSString stringWithFormat:
@"%@/%@%lld.%@",
[NSTemporaryDirectory() stringByStandardizingPath],
CDV_PHOTO_PREFIX,
(long long)([[NSDate date] timeIntervalSince1970] * 1000.0),
fileExtension];
}
- (NSString*)copyFileToTemp:(NSString*)filePath
{
NSFileManager* fileManager = [[NSFileManager alloc] init];
NSString* tempFilePath = [self tempFilePathForExtension:[filePath pathExtension]];
NSError *error = nil;
// Copy file to temp directory
BOOL copySuccess = [fileManager copyItemAtPath:filePath toPath:tempFilePath error:&error];
if (!copySuccess || error) {
NSLog(@"CDVCamera: Failed to copy file from %@ to temporary path %@. Error: %@", filePath, tempFilePath, [error localizedDescription]);
return nil;
}
// Verify the copied file exists
if (![fileManager fileExistsAtPath:tempFilePath]) {
NSLog(@"CDVCamera: Copied file does not exist at temporary path: %@", tempFilePath);
return nil;
}
return [[NSURL fileURLWithPath:tempFilePath] absoluteString];
}
/**
Called by JS camera.cleanup()
Removes intermediate image files that are kept in temporary storage after
calling camera.getPicture.
*/
- (void)cleanup:(CDVInvokedUrlCommand*)command
{
NSFileManager* fileManager = [NSFileManager defaultManager];
NSString* tempDirectoryPath = NSTemporaryDirectory();
NSError* error = nil;
NSArray<NSString*>* allFiles = [fileManager contentsOfDirectoryAtPath:tempDirectoryPath error:&error];
if (error) {
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION
messageAsString:[error localizedDescription]];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
return;
}
BOOL hasErrors = NO;
for (NSString* fileName in allFiles) {
// Only delete files created by the camera plugin
if (![fileName hasPrefix:CDV_PHOTO_PREFIX]) continue;
NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
NSError* deleteError = nil;
if (![fileManager removeItemAtPath:filePath error:&deleteError]) {
NSLog(@"Failed to delete: %@ (error: %@)", filePath, deleteError);
hasErrors = YES;
}
}
CDVPluginResult* result = hasErrors
? [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"One or more files failed to be deleted."]
: [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
#pragma mark UIImagePickerControllerDelegate methods
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
__weak CDVUIImagePickerController* cdvUIImagePickerController = (CDVUIImagePickerController*)picker;
__weak CDVCamera* weakSelf = self;
dispatch_block_t invoke = ^(void) {
__block CDVPluginResult* result = nil;
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
// Image selected
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
[weakSelf resultForImage:cdvUIImagePickerController.pictureOptions info:info completion:^(CDVPluginResult* res) {
if (![self usesGeolocation] || picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
[weakSelf.commandDelegate sendPluginResult:res callbackId:cdvUIImagePickerController.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.cdvUIImagePickerController = nil;
}
}];
// Video selected
} else {
result = [weakSelf resultForVideo:info];
[weakSelf.commandDelegate sendPluginResult:result callbackId:cdvUIImagePickerController.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.cdvUIImagePickerController = nil;
}
};
[[cdvUIImagePickerController presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
}
// older api calls newer didFinishPickingMediaWithInfo
- (void)imagePickerController:(UIImagePickerController*)picker
didFinishPickingImage:(UIImage*)image
editingInfo:(NSDictionary*)editingInfo
{
NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage];
[self imagePickerController:picker didFinishPickingMediaWithInfo:imageInfo];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
{
__weak CDVUIImagePickerController* cdvUIImagePickerController = (CDVUIImagePickerController*)picker;
__weak CDVCamera* weakSelf = self;
dispatch_block_t invoke = ^ (void) {
CDVPluginResult* result;
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No Image Selected"];
}
[weakSelf.commandDelegate sendPluginResult:result callbackId:cdvUIImagePickerController.callbackId];
weakSelf.hasPendingOperation = NO;
weakSelf.cdvUIImagePickerController = nil;
};
[[cdvUIImagePickerController presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
}
#pragma mark CLLocationManager
/**
Lazy instantiation of the CLLocationManager used to get GPS location data when
when capturing JPEGs.
@return The CLLocationManager instance.
*/
- (CLLocationManager*)locationManager
{
if (locationManager != nil) {
return locationManager;
}
locationManager = [[CLLocationManager alloc] init];
[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
[locationManager setDelegate:self];
return locationManager;
}
# pragma mark CLLocationManagerDelegate methods
/**
Called when the CLLocationManager has retrieved a location update.
The location data is formatted and added to the image metadata, and
the image result is returned. Only used when capturing JPEGs.
@param manager The CLLocationManager instance.
@param newLocation The new CLLocation data.
@param oldLocation The previous CLLocation data.
*/
- (void)locationManager:(CLLocationManager*)manager
didUpdateToLocation:(CLLocation*)newLocation
fromLocation:(CLLocation*)oldLocation
{
if (locationManager == nil) {
return;
}
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
CLLocationDegrees latitude = newLocation.coordinate.latitude;
CLLocationDegrees longitude = newLocation.coordinate.longitude;
// latitude
if (latitude < 0.0) {
latitude = latitude * -1.0f;
[GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
} else {
[GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
// longitude
if (longitude < 0.0) {
longitude = longitude * -1.0f;
[GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}
else {
[GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
// altitude
CGFloat altitude = newLocation.altitude;
if (!isnan(altitude)){
if (altitude < 0) {
altitude = -altitude;
[GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
} else {
[GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
}
[GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
}
// Time and date
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
[formatter setDateFormat:@"yyyy:MM:dd"];
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
[self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
[self returnImageAfterLocationUpdate];
}
/**
Called when the CLLocationManager fails to retrieve location data.
The image result is returned without location metadata.
Only used when capturing JPEGs.
@param manager The CLLocationManager instance.
@param error The error that occurred.
*/
- (void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)error
{
if (locationManager == nil) {
return;
}
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
[self returnImageAfterLocationUpdate];
}
/**
Called to return the image result after location data has been added to
`self.metadata` or an error occurred while retrieving location data.
TODO: The code is similar, what `resultForImage:` is doing and can be refactored:
*/
- (void)returnImageAfterLocationUpdate
{
CDVPictureOptions* pictureOptions = self.cdvUIImagePickerController.pictureOptions;
CDVPluginResult* pluginResult = nil;
NSData *imageDataToWrite = self.tempImageDataForLocationManager;
// Copy custom choosen meta data stored in self.metadata to the image
// This will also contain the fetched location from LocationManager
if (self.metadata) {
NSMutableData *imageDataWitCustomMetadata = [NSMutableData data];
// Prepare source image
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)imageDataToWrite, NULL);
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWitCustomMetadata, sourceType, 1, NULL);
// Copy choosen metadata
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
CGImageDestinationFinalize(destinationImage);
CFRelease(sourceImage);
CFRelease(destinationImage);
imageDataToWrite = imageDataWitCustomMetadata;
}
switch (pictureOptions.destinationType) {
case DestinationTypeDataUrl:
{
NSString* mime = [self getMimeForEncoding:pictureOptions.encodingType];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsString:[self formatAsDataURI:imageDataToWrite withMIME:mime]];
}
break;
default: // DestinationTypeFileUri
{
NSError* err = nil;
NSString* tempFilePath = [self tempFilePathForExtension:pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg"];
// Write image to temp file path
if ([imageDataToWrite writeToFile:tempFilePath options:NSAtomicWrite error:&err]) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsString:[[NSURL fileURLWithPath:tempFilePath] absoluteString]];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION
messageAsString:[err localizedDescription]];
}
}
break;
};
if (pluginResult) {
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.cdvUIImagePickerController.callbackId];
}
self.hasPendingOperation = NO;
self.cdvUIImagePickerController = nil;
self.tempImageDataForLocationManager = nil;
self.metadata = nil;
if (pictureOptions.saveToPhotoAlbum) {
UIImageWriteToSavedPhotosAlbum([[UIImage alloc] initWithData:imageDataToWrite], nil, nil, nil);
}
}
@end
@implementation CDVUIImagePickerController
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (UIViewController*)childViewControllerForStatusBarHidden
{
return nil;
}
- (void)viewWillAppear:(BOOL)animated
{
SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
if ([self respondsToSelector:sel]) {
[self performSelector:sel withObject:nil afterDelay:0];
}
[super viewWillAppear:animated];
}
+ (instancetype)createFromPictureOptions:(CDVPictureOptions*)pictureOptions
{
CDVUIImagePickerController* cdvUIImagePickerController = [[CDVUIImagePickerController alloc] init];
cdvUIImagePickerController.pictureOptions = pictureOptions;
cdvUIImagePickerController.sourceType = pictureOptions.sourceType;
cdvUIImagePickerController.allowsEditing = pictureOptions.allowsEditing;
if (cdvUIImagePickerController.sourceType == UIImagePickerControllerSourceTypeCamera) {
// We only allow taking pictures (no video) in this API.
cdvUIImagePickerController.mediaTypes = @[(NSString*)kUTTypeImage];
// We can only set the camera device if we're actually using the camera.
cdvUIImagePickerController.cameraDevice = pictureOptions.cameraDirection;
} else if (pictureOptions.mediaType == MediaTypeAll) {
cdvUIImagePickerController.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:cdvUIImagePickerController.sourceType];
} else {
NSArray* mediaArray = @[(NSString*)(pictureOptions.mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage)];
cdvUIImagePickerController.mediaTypes = mediaArray;
}
return cdvUIImagePickerController;
}
@end