mirror of
https://github.com/status-im/react-native-image-crop-picker.git
synced 2025-02-23 19:08:12 +00:00
277 lines
11 KiB
Objective-C
277 lines
11 KiB
Objective-C
//
|
|
// ImageManager.m
|
|
//
|
|
// Created by Ivan Pusic on 5/4/16.
|
|
// Copyright © 2016 Facebook. All rights reserved.
|
|
//
|
|
|
|
#import "ImageCropPicker.h"
|
|
|
|
#define ERROR_PICKER_CANCEL_KEY @"picker_cancel"
|
|
#define ERROR_PICKER_CANCEL_MSG @"User cancelled image selection"
|
|
|
|
#define ERROR_CANNOT_SAVE_IMAGE_KEY @"cannot_save_image"
|
|
#define ERROR_CANNOT_SAVE_IMAGE_MSG @"Cannot save image. Unable to write to tmp location."
|
|
|
|
@implementation ImageCropPicker
|
|
|
|
RCT_EXPORT_MODULE();
|
|
|
|
|
|
- (instancetype)init
|
|
{
|
|
if (self = [super init]) {
|
|
self.defaultOptions = @{
|
|
@"multiple": @NO,
|
|
@"cropping": @NO,
|
|
@"maxFiles": @5,
|
|
@"width": @200,
|
|
@"height": @200
|
|
};
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
|
|
self.resolve = resolve;
|
|
self.reject = reject;
|
|
self.options = [NSMutableDictionary dictionaryWithDictionary:self.defaultOptions];
|
|
for (NSString *key in options.keyEnumerator) {
|
|
[self.options setValue:options[key] forKey:key];
|
|
}
|
|
|
|
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// init picker
|
|
QBImagePickerController *imagePickerController =
|
|
[QBImagePickerController new];
|
|
imagePickerController.delegate = self;
|
|
imagePickerController.allowsMultipleSelection = [[self.options objectForKey:@"multiple"] boolValue];
|
|
imagePickerController.maximumNumberOfSelection = [[self.options objectForKey:@"maxFiles"] intValue];
|
|
imagePickerController.showsNumberOfSelectedAssets = YES;
|
|
imagePickerController.mediaType = QBImagePickerMediaTypeImage;
|
|
|
|
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];
|
|
}
|
|
});
|
|
});
|
|
}];
|
|
}
|
|
|
|
- (void)qb_imagePickerController:
|
|
(QBImagePickerController *)imagePickerController
|
|
didFinishPickingAssets:(NSArray *)assets {
|
|
|
|
PHImageManager *manager = [PHImageManager defaultManager];
|
|
|
|
if ([[[self options] objectForKey:@"multiple"] boolValue]) {
|
|
NSMutableArray *images = [[NSMutableArray alloc] init];
|
|
PHImageRequestOptions* options = [[PHImageRequestOptions alloc] init];
|
|
options.synchronous = YES;
|
|
|
|
for (PHAsset *asset in assets) {
|
|
[manager
|
|
requestImageDataForAsset:asset
|
|
options:options
|
|
resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
|
|
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;
|
|
}
|
|
|
|
[images addObject:@{
|
|
@"path": filePath,
|
|
@"width": @(asset.pixelWidth),
|
|
@"height": @(asset.pixelHeight),
|
|
@"mime": @"image/jpeg",
|
|
@"size": [NSNumber numberWithUnsignedInteger:data.length]
|
|
}];
|
|
}];
|
|
}
|
|
|
|
self.resolve(images);
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:nil];
|
|
} else {
|
|
PHAsset *asset = [assets objectAtIndex:0];
|
|
|
|
[manager
|
|
requestImageDataForAsset:asset
|
|
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]
|
|
});
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:nil];
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)qb_imagePickerControllerDidCancel:(QBImagePickerController *)imagePickerController {
|
|
self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, nil);
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:nil];
|
|
}
|
|
|
|
#pragma mark - CustomCropModeDelegates
|
|
|
|
// Returns a custom rect for the mask.
|
|
- (CGRect)imageCropViewControllerCustomMaskRect:
|
|
(RSKImageCropViewController *)controller {
|
|
CGSize maskSize = CGSizeMake(
|
|
[[self.options objectForKey:@"width"] intValue],
|
|
[[self.options objectForKey:@"height"] intValue]);
|
|
|
|
CGFloat viewWidth = CGRectGetWidth(controller.view.frame);
|
|
CGFloat viewHeight = CGRectGetHeight(controller.view.frame);
|
|
|
|
CGRect maskRect = CGRectMake((viewWidth - maskSize.width) * 0.5f,
|
|
(viewHeight - maskSize.height) * 0.5f,
|
|
maskSize.width, maskSize.height);
|
|
|
|
return maskRect;
|
|
}
|
|
|
|
// if provided width or height is bigger than screen w/h,
|
|
// then we should scale draw area
|
|
- (CGRect) scaleRect:(RSKImageCropViewController *)controller {
|
|
CGRect rect = controller.maskRect;
|
|
CGFloat viewWidth = CGRectGetWidth(controller.view.frame);
|
|
CGFloat viewHeight = CGRectGetHeight(controller.view.frame);
|
|
|
|
double scaleFactor = fmin(viewWidth / rect.size.width, viewHeight / rect.size.height);
|
|
rect.size.width *= scaleFactor;
|
|
rect.size.height *= scaleFactor;
|
|
rect.origin.x = (viewWidth - rect.size.width) / 2;
|
|
rect.origin.y = (viewHeight - rect.size.height) / 2;
|
|
|
|
return rect;
|
|
}
|
|
|
|
// Returns a custom path for the mask.
|
|
- (UIBezierPath *)imageCropViewControllerCustomMaskPath:
|
|
(RSKImageCropViewController *)controller {
|
|
CGRect rect = [self scaleRect:controller];
|
|
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect
|
|
byRoundingCorners:UIRectCornerAllCorners
|
|
cornerRadii:CGSizeMake(0, 0)];
|
|
return path;
|
|
}
|
|
|
|
// Returns a custom rect in which the image can be moved.
|
|
- (CGRect)imageCropViewControllerCustomMovementRect:
|
|
(RSKImageCropViewController *)controller {
|
|
return [self scaleRect:controller];
|
|
}
|
|
|
|
#pragma mark - CropFinishDelegate
|
|
|
|
// Crop image has been canceled.
|
|
- (void)imageCropViewControllerDidCancelCrop:
|
|
(RSKImageCropViewController *)controller {
|
|
self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, nil);
|
|
[controller dismissViewControllerAnimated:YES completion:nil];
|
|
}
|
|
|
|
// The original image has been cropped.
|
|
- (void)imageCropViewController:(RSKImageCropViewController *)controller
|
|
didCropImage:(UIImage *)croppedImage
|
|
usingCropRect:(CGRect)cropRect {
|
|
|
|
// we have correct rect, but not correct dimensions
|
|
// so resize image
|
|
CGSize resizedImageSize = CGSizeMake([[[self options] objectForKey:@"width"] intValue], [[[self options] objectForKey:@"height"] intValue]);
|
|
UIImage *resizedImage = [croppedImage resizedImageToFitInSize:resizedImageSize scaleIfSmaller:YES];
|
|
NSData *data = UIImageJPEGRepresentation(resizedImage, 1);
|
|
|
|
NSString *filePath = [self persistFile:data];
|
|
if (filePath == nil) {
|
|
self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil);
|
|
return;
|
|
}
|
|
|
|
NSDictionary *image = @{
|
|
@"path": filePath,
|
|
@"width": @(resizedImage.size.width),
|
|
@"height": @(resizedImage.size.height),
|
|
@"mime": @"image/jpeg",
|
|
@"size": [NSNumber numberWithUnsignedInteger:data.length]
|
|
};
|
|
|
|
self.resolve(image);
|
|
[controller dismissViewControllerAnimated:YES completion:nil];
|
|
}
|
|
|
|
// at the moment it is not possible to upload image by reading PHAsset
|
|
// we are saving image and saving it to the tmp location where we are allowed to access image later
|
|
- (NSString*) persistFile:(NSData*)data {
|
|
// create temp file
|
|
NSString *filePath = [NSTemporaryDirectory() stringByAppendingString:[[NSUUID UUID] UUIDString]];
|
|
filePath = [filePath stringByAppendingString:@".jpg"];
|
|
|
|
// save cropped file
|
|
BOOL status = [data writeToFile:filePath atomically:YES];
|
|
if (!status) {
|
|
return nil;
|
|
}
|
|
|
|
return filePath;
|
|
}
|
|
|
|
// The original image has been cropped. Additionally provides a rotation angle
|
|
// used to produce image.
|
|
- (void)imageCropViewController:(RSKImageCropViewController *)controller
|
|
didCropImage:(UIImage *)croppedImage
|
|
usingCropRect:(CGRect)cropRect
|
|
rotationAngle:(CGFloat)rotationAngle {
|
|
[self imageCropViewController:controller didCropImage:croppedImage usingCropRect:cropRect];
|
|
}
|
|
|
|
@end
|