From f1d574ca0b4c2159c820e55f6dc6cdcea6dd9473 Mon Sep 17 00:00:00 2001 From: Ivan Pusic Date: Thu, 11 Aug 2016 20:00:59 +0200 Subject: [PATCH] [iOS] Camera support --- example/app.js | 20 +++ example/ios/example.xcodeproj/project.pbxproj | 4 +- ios/ImageCropPicker.m | 166 ++++++++++++------ 3 files changed, 139 insertions(+), 51 deletions(-) diff --git a/example/app.js b/example/app.js index ef3bbcd..8e8cfb1 100644 --- a/example/app.js +++ b/example/app.js @@ -30,6 +30,20 @@ export default class App extends Component { }; } + pickSingleWithCamera(cropping) { + ImagePicker.openCamera({ + cropping, + width: 500, + height: 500 + }).then(image => { + console.log('received image', image); + this.setState({ + image: {uri: image.path, width: image.width, height: image.height}, + images: null + }); + }).catch(e => alert(e)); + } + pickSingleBase64(cropit) { ImagePicker.openPicker({ width: 300, @@ -84,6 +98,12 @@ export default class App extends Component { {this.state.images ? this.state.images.map(i => ) : null} + this.pickSingleWithCamera(false)} style={styles.button}> + Select Single With Camera + + this.pickSingleWithCamera(true)} style={styles.button}> + Select Single With Camera With Cropping + this.pickSingle(false)} style={styles.button}> Select Single diff --git a/example/ios/example.xcodeproj/project.pbxproj b/example/ios/example.xcodeproj/project.pbxproj index 46bec21..42c5aa5 100644 --- a/example/ios/example.xcodeproj/project.pbxproj +++ b/example/ios/example.xcodeproj/project.pbxproj @@ -400,7 +400,7 @@ "$(inherited)", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.imagecroppikcer.example; + PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.imagecropikcer.example; PRODUCT_NAME = example; PROVISIONING_PROFILE = ""; }; @@ -426,7 +426,7 @@ "$(inherited)", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.imagecroppikcer.example; + PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.imagecropikcer.example; PRODUCT_NAME = example; PROVISIONING_PROFILE = ""; }; diff --git a/ios/ImageCropPicker.m b/ios/ImageCropPicker.m index 4d71d7e..915a027 100644 --- a/ios/ImageCropPicker.m +++ b/ios/ImageCropPicker.m @@ -7,6 +7,12 @@ #import "ImageCropPicker.h" +#define ERROR_PICKER_CANNOT_RUN_CAMERA_ON_SIMULATOR_KEY @"cannot_run_camera_on_simulator" +#define ERROR_PICKER_CANNOT_RUN_CAMERA_ON_SIMULATOR_MSG @"Cannot run camera on simulator" + +#define ERROR_PICKER_NO_CAMERA_PERMISSION_KEY @"missing_camera_permission" +#define ERROR_PICKER_NO_CAMERA_PERMISSION_MSG @"User did not grant camera permission." + #define ERROR_PICKER_CANCEL_KEY @"picker_cancel" #define ERROR_PICKER_CANCEL_MSG @"User cancelled image selection" @@ -33,9 +39,25 @@ RCT_EXPORT_MODULE(); return self; } -RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { +- (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) setConfiguration:(NSDictionary *)options + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject { self.resolve = resolve; self.reject = reject; @@ -43,7 +65,59 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options for (NSString *key in options.keyEnumerator) { [self.options setValue:options[key] forKey:key]; } +} + +- (UIViewController*) getRootVC { + UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + while (root.presentedViewController != nil) { + root = root.presentedViewController; + } + return root; +} + +RCT_EXPORT_METHOD(openCamera:(NSDictionary *)options + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + + [self setConfiguration:options resolver:resolve rejecter:reject]; + +#if TARGET_IPHONE_SIMULATOR + self.reject(ERROR_PICKER_CANNOT_RUN_CAMERA_ON_SIMULATOR_KEY, ERROR_PICKER_CANNOT_RUN_CAMERA_ON_SIMULATOR_MSG, nil); + return; +#else + [self checkCameraPermissions:^(BOOL granted) { + if (!granted) { + self.reject(ERROR_PICKER_NO_CAMERA_PERMISSION_KEY, ERROR_PICKER_NO_CAMERA_PERMISSION_MSG, nil); + return; + } + + UIImagePickerController *picker = [[UIImagePickerController alloc] init]; + picker.delegate = self; + picker.allowsEditing = NO; + picker.sourceType = UIImagePickerControllerSourceTypeCamera; + picker.delegate = self; + + [[self getRootVC] presentViewController:picker animated:YES completion:nil]; + }]; +#endif +} + +- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { + UIImage *chosenImage = [info objectForKey:UIImagePickerControllerOriginalImage]; + [self processSingleImagePick:chosenImage withViewController:picker]; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { + self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, nil); + [picker dismissViewControllerAnimated:YES completion:NULL]; +} + +RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + + [self setConfiguration:options resolver:resolve rejecter:reject]; [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ // init picker @@ -57,11 +131,7 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; dispatch_async(dispatch_get_main_queue(), ^{ - if (root.presentedViewController) { - [root.presentedViewController presentViewController:imagePickerController animated:YES completion:nil]; - } else { - [root presentViewController:imagePickerController animated:YES completion:nil]; - } + [[self getRootVC] presentViewController:imagePickerController animated:YES completion:nil]; }); }); }]; @@ -106,52 +176,13 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options self.resolve(images); [imagePickerController dismissViewControllerAnimated:YES completion:nil]; } else { - PHAsset *asset = [assets objectAtIndex:0]; - [manager - requestImageDataForAsset:asset + requestImageDataForAsset:[assets objectAtIndex:0] options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { - - if ([[[self options] objectForKey:@"cropping"] boolValue]) { - UIImage *image = [UIImage imageWithData:imageData]; - RSKImageCropViewController *imageCropVC = [[RSKImageCropViewController alloc] initWithImage:image cropMode:RSKImageCropModeCustom]; - - imageCropVC.avoidEmptySpaceAroundImage = YES; - imageCropVC.dataSource = self; - imageCropVC.delegate = self; - - [imagePickerController dismissViewControllerAnimated:YES completion:^{ - UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - dispatch_async(dispatch_get_main_queue(), ^{ - if (root.presentedViewController) { - [root.presentedViewController presentViewController:imageCropVC animated:YES completion:nil]; - } else { - [root presentViewController:imageCropVC animated:YES completion:nil]; - } - }); - }]; - } else { - UIImage *image = [UIImage imageWithData:imageData]; - NSData *data = UIImageJPEGRepresentation(image, 1); - NSString *filePath = [self persistFile:data]; - if (filePath == nil) { - self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil); - return; - } - - self.resolve(@{ - @"path": filePath, - @"width": @(asset.pixelWidth), - @"height": @(asset.pixelHeight), - @"mime": @"image/jpeg", - @"size": [NSNumber numberWithUnsignedInteger:data.length], - @"data": [[self.options objectForKey:@"includeBase64"] boolValue] ? [data base64EncodedStringWithOptions:0] : [NSNull null], - }); - [imagePickerController dismissViewControllerAnimated:YES completion:nil]; - } + [self processSingleImagePick:[UIImage imageWithData:imageData] withViewController:imagePickerController]; }]; } } @@ -161,6 +192,43 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options [imagePickerController dismissViewControllerAnimated:YES completion:nil]; } +// when user selected single image, with camera or from photo gallery, +// this method will take care of attaching image metadata, and sending image to cropping controller +// or to user directly +- (void) processSingleImagePick:(UIImage*)image withViewController:(UIViewController*)viewController { + if ([[[self options] objectForKey:@"cropping"] boolValue]) { + RSKImageCropViewController *imageCropVC = [[RSKImageCropViewController alloc] initWithImage:image cropMode:RSKImageCropModeCustom]; + + imageCropVC.avoidEmptySpaceAroundImage = YES; + imageCropVC.dataSource = self; + imageCropVC.delegate = self; + + [viewController dismissViewControllerAnimated:YES completion:^{ + UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[self getRootVC] presentViewController:imageCropVC animated:YES completion:nil]; + }); + }]; + } else { + NSData *data = UIImageJPEGRepresentation(image, 1); + NSString *filePath = [self persistFile:data]; + if (filePath == nil) { + self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil); + return; + } + + self.resolve(@{ + @"path": filePath, + @"width": @(image.size.width), + @"height": @(image.size.height), + @"mime": @"image/jpeg", + @"size": [NSNumber numberWithUnsignedInteger:data.length], + @"data": [[self.options objectForKey:@"includeBase64"] boolValue] ? [data base64EncodedStringWithOptions:0] : [NSNull null], + }); + [viewController dismissViewControllerAnimated:YES completion:nil]; + } +} + #pragma mark - CustomCropModeDelegates // Returns a custom rect for the mask.