mirror of
https://github.com/status-im/react-native-image-crop-picker.git
synced 2025-02-23 10:58:16 +00:00
701 lines
30 KiB
Objective-C
701 lines
30 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_CANNOT_RUN_CAMERA_ON_SIMULATOR_KEY @"E_PICKER_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 @"E_PICKER_NO_CAMERA_PERMISSION"
|
|
#define ERROR_PICKER_NO_CAMERA_PERMISSION_MSG @"User did not grant camera permission."
|
|
|
|
#define ERROR_PICKER_UNAUTHORIZED_KEY @"ERROR_PICKER_UNAUTHORIZED_KEY"
|
|
#define ERROR_PICKER_UNAUTHORIZED_MSG @"Cannot access images. Please allow access if you want to be able to select images."
|
|
|
|
#define ERROR_PICKER_CANCEL_KEY @"E_PICKER_CANCELLED"
|
|
#define ERROR_PICKER_CANCEL_MSG @"User cancelled image selection"
|
|
|
|
#define ERROR_PICKER_NO_DATA_KEY @"ERROR_PICKER_NO_DATA"
|
|
#define ERROR_PICKER_NO_DATA_MSG @"Cannot find image data"
|
|
|
|
#define ERROR_CROPPER_IMAGE_NOT_FOUND_KEY @"ERROR_CROPPER_IMAGE_NOT_FOUND"
|
|
#define ERROR_CROPPER_IMAGE_NOT_FOUND_MSG @"Can't find the image at the specified path"
|
|
|
|
#define ERROR_CLEANUP_ERROR_KEY @"E_ERROR_WHILE_CLEANING_FILES"
|
|
#define ERROR_CLEANUP_ERROR_MSG @"Error while cleaning up tmp files"
|
|
|
|
#define ERROR_CANNOT_SAVE_IMAGE_KEY @"E_CANNOT_SAVE_IMAGE"
|
|
#define ERROR_CANNOT_SAVE_IMAGE_MSG @"Cannot save image. Unable to write to tmp location."
|
|
|
|
#define ERROR_CANNOT_PROCESS_VIDEO_KEY @"E_CANNOT_PROCESS_VIDEO"
|
|
#define ERROR_CANNOT_PROCESS_VIDEO_MSG @"Cannot process video data"
|
|
|
|
@implementation ImageResult
|
|
@end
|
|
|
|
@implementation ImageCropPicker
|
|
|
|
RCT_EXPORT_MODULE();
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
- (instancetype)init
|
|
{
|
|
if (self = [super init]) {
|
|
self.defaultOptions = @{
|
|
@"multiple": @NO,
|
|
@"cropping": @NO,
|
|
@"cropperCircleOverlay": @NO,
|
|
@"includeBase64": @NO,
|
|
@"compressVideo": @YES,
|
|
@"maxFiles": @5,
|
|
@"width": @200,
|
|
@"waitAnimationEnd": @YES,
|
|
@"height": @200,
|
|
@"useFrontCamera": @NO,
|
|
@"compressImageQuality": @1,
|
|
@"compressVideoPreset": @"MediumQuality",
|
|
@"loadingLabelText": @"Processing assets...",
|
|
@"mediaType": @"any",
|
|
@"showsSelectedCount": @YES
|
|
};
|
|
self.compression = [[Compression alloc] init];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void (^ __nullable)(void))waitAnimationEnd:(void (^ __nullable)(void))completion {
|
|
if ([[self.options objectForKey:@"waitAnimationEnd"] boolValue]) {
|
|
return completion;
|
|
}
|
|
|
|
if (completion != nil) {
|
|
completion();
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (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;
|
|
self.options = [NSMutableDictionary dictionaryWithDictionary:self.defaultOptions];
|
|
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];
|
|
self.cropOnly = NO;
|
|
|
|
#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;
|
|
if ([[self.options objectForKey:@"useFrontCamera"] boolValue]) {
|
|
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
|
|
}
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[[self getRootVC] presentViewController:picker animated:YES completion:nil];
|
|
});
|
|
}];
|
|
#endif
|
|
}
|
|
|
|
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
|
|
UIImage *chosenImage = [info objectForKey:UIImagePickerControllerOriginalImage];
|
|
UIImage *chosenImageT = [chosenImage fixOrientation];
|
|
[self processSingleImagePick:chosenImageT withViewController:picker];
|
|
}
|
|
|
|
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
|
|
[picker dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, nil);
|
|
}]];
|
|
}
|
|
|
|
- (NSString*) getTmpDirectory {
|
|
NSString *TMP_DIRECTORY = @"react-native-image-crop-picker/";
|
|
NSString *tmpFullPath = [NSTemporaryDirectory() stringByAppendingString:TMP_DIRECTORY];
|
|
|
|
BOOL isDir;
|
|
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:tmpFullPath isDirectory:&isDir];
|
|
if (!exists) {
|
|
[[NSFileManager defaultManager] createDirectoryAtPath: tmpFullPath
|
|
withIntermediateDirectories:YES attributes:nil error:nil];
|
|
}
|
|
|
|
return tmpFullPath;
|
|
}
|
|
|
|
- (BOOL)cleanTmpDirectory {
|
|
NSString* tmpDirectoryPath = [self getTmpDirectory];
|
|
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpDirectoryPath error:NULL];
|
|
|
|
for (NSString *file in tmpDirectory) {
|
|
BOOL deleted = [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", tmpDirectoryPath, file] error:NULL];
|
|
|
|
if (!deleted) {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(cleanSingle:(NSString *) path
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
|
|
BOOL deleted = [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
|
|
|
|
if (!deleted) {
|
|
reject(ERROR_CLEANUP_ERROR_KEY, ERROR_CLEANUP_ERROR_MSG, nil);
|
|
} else {
|
|
resolve(nil);
|
|
}
|
|
}
|
|
|
|
RCT_REMAP_METHOD(clean, resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
if (![self cleanTmpDirectory]) {
|
|
reject(ERROR_CLEANUP_ERROR_KEY, ERROR_CLEANUP_ERROR_MSG, nil);
|
|
} else {
|
|
resolve(nil);
|
|
}
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
|
|
[self setConfiguration:options resolver:resolve rejecter:reject];
|
|
self.cropOnly = NO;
|
|
|
|
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
|
|
if (status != PHAuthorizationStatusAuthorized) {
|
|
self.reject(ERROR_PICKER_UNAUTHORIZED_KEY, ERROR_PICKER_UNAUTHORIZED_MSG, nil);
|
|
return;
|
|
}
|
|
|
|
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 = [[self.options objectForKey:@"showsSelectedCount"] boolValue];
|
|
|
|
if ([self.options objectForKey:@"smartAlbums"] != nil) {
|
|
NSDictionary *smartAlbums = @{
|
|
@"UserLibrary" : @(PHAssetCollectionSubtypeSmartAlbumUserLibrary),
|
|
@"PhotoStream" : @(PHAssetCollectionSubtypeAlbumMyPhotoStream),
|
|
@"Panoramas" : @(PHAssetCollectionSubtypeSmartAlbumPanoramas),
|
|
@"Videos" : @(PHAssetCollectionSubtypeSmartAlbumVideos),
|
|
@"Bursts" : @(PHAssetCollectionSubtypeSmartAlbumBursts),
|
|
};
|
|
NSMutableArray *albumsToShow = [NSMutableArray arrayWithCapacity:5];
|
|
for (NSString* album in [self.options objectForKey:@"smartAlbums"]) {
|
|
if ([smartAlbums objectForKey:album] != nil) {
|
|
[albumsToShow addObject:[smartAlbums objectForKey:album]];
|
|
}
|
|
}
|
|
imagePickerController.assetCollectionSubtypes = albumsToShow;
|
|
}
|
|
|
|
if ([[self.options objectForKey:@"cropping"] boolValue]) {
|
|
imagePickerController.mediaType = QBImagePickerMediaTypeImage;
|
|
} else {
|
|
NSString *mediaType = [self.options objectForKey:@"mediaType"];
|
|
|
|
if ([mediaType isEqualToString:@"any"]) {
|
|
imagePickerController.mediaType = QBImagePickerMediaTypeAny;
|
|
} else if ([mediaType isEqualToString:@"photo"]) {
|
|
imagePickerController.mediaType = QBImagePickerMediaTypeImage;
|
|
} else {
|
|
imagePickerController.mediaType = QBImagePickerMediaTypeVideo;
|
|
}
|
|
|
|
}
|
|
|
|
[[self getRootVC] presentViewController:imagePickerController animated:YES completion:nil];
|
|
});
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
|
|
[self setConfiguration:options resolver:resolve rejecter:reject];
|
|
self.cropOnly = YES;
|
|
|
|
NSString *path = [options objectForKey:@"path"];
|
|
NSURL *url = [NSURL URLWithString:path];
|
|
|
|
[self.bridge.imageLoader loadImageWithURLRequest:[RCTConvert NSURLRequest:path] callback:^(NSError *error, UIImage *image) {
|
|
if (error) {
|
|
self.reject(ERROR_CROPPER_IMAGE_NOT_FOUND_KEY, ERROR_CROPPER_IMAGE_NOT_FOUND_MSG, nil);
|
|
} else {
|
|
[self startCropping:image];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)startCropping:(UIImage *)image {
|
|
RSKImageCropViewController *imageCropVC = [[RSKImageCropViewController alloc] initWithImage:image];
|
|
if ([[[self options] objectForKey:@"cropperCircleOverlay"] boolValue]) {
|
|
imageCropVC.cropMode = RSKImageCropModeCircle;
|
|
} else {
|
|
imageCropVC.cropMode = RSKImageCropModeCustom;
|
|
}
|
|
imageCropVC.avoidEmptySpaceAroundImage = YES;
|
|
imageCropVC.dataSource = self;
|
|
imageCropVC.delegate = self;
|
|
[imageCropVC setModalPresentationStyle:UIModalPresentationCustom];
|
|
[imageCropVC setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[[self getRootVC] presentViewController:imageCropVC animated:YES completion:nil];
|
|
});
|
|
}
|
|
|
|
- (void)showActivityIndicator:(void (^)(UIActivityIndicatorView*, UIView*))handler {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
UIView *mainView = [[self getRootVC] view];
|
|
|
|
// create overlay
|
|
UIView *loadingView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
loadingView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
|
|
loadingView.clipsToBounds = YES;
|
|
|
|
// create loading spinner
|
|
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
|
activityView.frame = CGRectMake(65, 40, activityView.bounds.size.width, activityView.bounds.size.height);
|
|
activityView.center = loadingView.center;
|
|
[loadingView addSubview:activityView];
|
|
|
|
// create message
|
|
UILabel *loadingLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 115, 130, 22)];
|
|
loadingLabel.backgroundColor = [UIColor clearColor];
|
|
loadingLabel.textColor = [UIColor whiteColor];
|
|
loadingLabel.adjustsFontSizeToFitWidth = YES;
|
|
CGPoint loadingLabelLocation = loadingView.center;
|
|
loadingLabelLocation.y += [activityView bounds].size.height;
|
|
loadingLabel.center = loadingLabelLocation;
|
|
loadingLabel.textAlignment = UITextAlignmentCenter;
|
|
loadingLabel.text = [self.options objectForKey:@"loadingLabelText"];
|
|
[loadingLabel setFont:[UIFont boldSystemFontOfSize:18]];
|
|
[loadingView addSubview:loadingLabel];
|
|
|
|
// show all
|
|
[mainView addSubview:loadingView];
|
|
[activityView startAnimating];
|
|
|
|
handler(activityView, loadingView);
|
|
});
|
|
}
|
|
|
|
|
|
- (void) getVideoAsset:(PHAsset*)forAsset completion:(void (^)(NSDictionary* image))completion {
|
|
PHImageManager *manager = [PHImageManager defaultManager];
|
|
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
|
|
options.version = PHVideoRequestOptionsVersionOriginal;
|
|
|
|
[manager
|
|
requestAVAssetForVideo:forAsset
|
|
options:options
|
|
resultHandler:^(AVAsset * asset, AVAudioMix * audioMix,
|
|
NSDictionary *info) {
|
|
NSURL *sourceURL = [(AVURLAsset *)asset URL];
|
|
|
|
// create temp file
|
|
NSString *tmpDirFullPath = [self getTmpDirectory];
|
|
NSString *filePath = [tmpDirFullPath stringByAppendingString:[[NSUUID UUID] UUIDString]];
|
|
filePath = [filePath stringByAppendingString:@".mp4"];
|
|
NSURL *outputURL = [NSURL fileURLWithPath:filePath];
|
|
|
|
[self.compression compressVideo:sourceURL outputURL:outputURL withOptions:self.options handler:^(AVAssetExportSession *exportSession) {
|
|
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
|
|
AVAsset *compressedAsset = [AVAsset assetWithURL:outputURL];
|
|
AVAssetTrack *track = [[compressedAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
|
|
|
|
NSNumber *fileSizeValue = nil;
|
|
[outputURL getResourceValue:&fileSizeValue
|
|
forKey:NSURLFileSizeKey
|
|
error:nil];
|
|
|
|
completion([self createAttachmentResponse:[outputURL absoluteString]
|
|
withWidth:[NSNumber numberWithFloat:track.naturalSize.width]
|
|
withHeight:[NSNumber numberWithFloat:track.naturalSize.height]
|
|
withMime:@"video/mp4"
|
|
withSize:fileSizeValue
|
|
withData:[NSNull null]]);
|
|
} else {
|
|
completion(nil);
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (NSDictionary*) createAttachmentResponse:(NSString*)filePath withWidth:(NSNumber*)width withHeight:(NSNumber*)height withMime:(NSString*)mime withSize:(NSNumber*)size withData:(NSString*)data {
|
|
return @{
|
|
@"path": filePath,
|
|
@"width": width,
|
|
@"height": height,
|
|
@"mime": mime,
|
|
@"size": size,
|
|
@"data": data,
|
|
};
|
|
}
|
|
|
|
- (void)qb_imagePickerController:
|
|
(QBImagePickerController *)imagePickerController
|
|
didFinishPickingAssets:(NSArray *)assets {
|
|
|
|
PHImageManager *manager = [PHImageManager defaultManager];
|
|
PHImageRequestOptions* options = [[PHImageRequestOptions alloc] init];
|
|
options.synchronous = NO;
|
|
options.networkAccessAllowed = YES;
|
|
|
|
if ([[[self options] objectForKey:@"multiple"] boolValue]) {
|
|
NSMutableArray *selections = [[NSMutableArray alloc] init];
|
|
|
|
[self showActivityIndicator:^(UIActivityIndicatorView *indicatorView, UIView *overlayView) {
|
|
NSLock *lock = [[NSLock alloc] init];
|
|
__block int processed = 0;
|
|
|
|
for (PHAsset *phAsset in assets) {
|
|
|
|
if (phAsset.mediaType == PHAssetMediaTypeVideo) {
|
|
[self getVideoAsset:phAsset completion:^(NSDictionary* video) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[lock lock];
|
|
|
|
if (video == nil) {
|
|
[indicatorView stopAnimating];
|
|
[overlayView removeFromSuperview];
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_CANNOT_PROCESS_VIDEO_KEY, ERROR_CANNOT_PROCESS_VIDEO_MSG, nil);
|
|
}]];
|
|
return;
|
|
}
|
|
|
|
[selections addObject:video];
|
|
processed++;
|
|
[lock unlock];
|
|
|
|
if (processed == [assets count]) {
|
|
[indicatorView stopAnimating];
|
|
[overlayView removeFromSuperview];
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.resolve(selections);
|
|
}]];
|
|
return;
|
|
}
|
|
});
|
|
}];
|
|
} else {
|
|
[manager
|
|
requestImageDataForAsset:phAsset
|
|
options:options
|
|
resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[lock lock];
|
|
UIImage *imgT = [UIImage imageWithData:imageData];
|
|
UIImage *imageT = [imgT fixOrientation];
|
|
|
|
ImageResult *imageResult = [self.compression compressImage:imageT withOptions:self.options];
|
|
NSString *filePath = [self persistFile:imageResult.data];
|
|
|
|
if (filePath == nil) {
|
|
[indicatorView stopAnimating];
|
|
[overlayView removeFromSuperview];
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil);
|
|
}]];
|
|
return;
|
|
}
|
|
|
|
[selections addObject:[self createAttachmentResponse:filePath
|
|
withWidth:imageResult.width
|
|
withHeight:imageResult.height
|
|
withMime:imageResult.mime
|
|
withSize:[NSNumber numberWithUnsignedInteger:imageResult.data.length]
|
|
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : [NSNull null]
|
|
]];
|
|
processed++;
|
|
[lock unlock];
|
|
|
|
if (processed == [assets count]) {
|
|
|
|
[indicatorView stopAnimating];
|
|
[overlayView removeFromSuperview];
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.resolve(selections);
|
|
}]];
|
|
return;
|
|
}
|
|
});
|
|
}];
|
|
}
|
|
}
|
|
}];
|
|
} else {
|
|
PHAsset *phAsset = [assets objectAtIndex:0];
|
|
|
|
[self showActivityIndicator:^(UIActivityIndicatorView *indicatorView, UIView *overlayView) {
|
|
if (phAsset.mediaType == PHAssetMediaTypeVideo) {
|
|
[self getVideoAsset:phAsset completion:^(NSDictionary* video) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[indicatorView stopAnimating];
|
|
[overlayView removeFromSuperview];
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
if (video != nil) {
|
|
self.resolve(video);
|
|
} else {
|
|
self.reject(ERROR_CANNOT_PROCESS_VIDEO_KEY, ERROR_CANNOT_PROCESS_VIDEO_MSG, nil);
|
|
}
|
|
}]];
|
|
});
|
|
}];
|
|
} else {
|
|
[manager
|
|
requestImageDataForAsset:phAsset
|
|
options:options
|
|
resultHandler:^(NSData *imageData, NSString *dataUTI,
|
|
UIImageOrientation orientation,
|
|
NSDictionary *info) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[indicatorView stopAnimating];
|
|
[overlayView removeFromSuperview];
|
|
[self processSingleImagePick:[UIImage imageWithData:imageData] withViewController:imagePickerController];
|
|
});
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)qb_imagePickerControllerDidCancel:(QBImagePickerController *)imagePickerController {
|
|
[imagePickerController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, 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 (image == nil) {
|
|
[viewController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_PICKER_NO_DATA_KEY, ERROR_PICKER_NO_DATA_MSG, nil);
|
|
}]];
|
|
return;
|
|
}
|
|
|
|
if ([[[self options] objectForKey:@"cropping"] boolValue]) {
|
|
[self startCropping:image];
|
|
} else {
|
|
ImageResult *imageResult = [self.compression compressImage:image withOptions:self.options];
|
|
NSString *filePath = [self persistFile:imageResult.data];
|
|
if (filePath == nil) {
|
|
[viewController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil);
|
|
}]];
|
|
return;
|
|
}
|
|
|
|
// Wait for viewController to dismiss before resolving, or we lose the ability to display
|
|
// Alert.alert in the .then() handler.
|
|
[viewController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
|
|
self.resolve([self createAttachmentResponse:filePath
|
|
withWidth:imageResult.width
|
|
withHeight:imageResult.height
|
|
withMime:imageResult.mime
|
|
withSize:[NSNumber numberWithUnsignedInteger:imageResult.data.length]
|
|
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : [NSNull null]]);
|
|
}]];
|
|
}
|
|
}
|
|
|
|
#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 dismissCropper:controller completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, nil);
|
|
}]];
|
|
}
|
|
|
|
- (void) dismissCropper:(RSKImageCropViewController*) controller completion:(void (^)())completion {
|
|
//We've presented the cropper on top of the image picker as to not have a double modal animation.
|
|
//Thus, we need to dismiss the image picker view controller to dismiss the whole stack.
|
|
if (!self.cropOnly) {
|
|
UIViewController *topViewController = controller.presentingViewController.presentingViewController;
|
|
[topViewController dismissViewControllerAnimated:YES completion:completion];
|
|
} else {
|
|
[controller dismissViewControllerAnimated:YES completion:completion];
|
|
}
|
|
}
|
|
|
|
// 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];
|
|
ImageResult *imageResult = [self.compression compressImage:resizedImage withOptions:self.options];
|
|
|
|
NSString *filePath = [self persistFile:imageResult.data];
|
|
if (filePath == nil) {
|
|
[self dismissCropper:controller completion:[self waitAnimationEnd:^{
|
|
self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil);
|
|
}]];
|
|
return;
|
|
}
|
|
|
|
[self dismissCropper:controller completion:[self waitAnimationEnd:^{
|
|
self.resolve([self createAttachmentResponse:filePath
|
|
withWidth:imageResult.width
|
|
withHeight:imageResult.height
|
|
withMime:imageResult.mime
|
|
withSize:[NSNumber numberWithUnsignedInteger:imageResult.data.length]
|
|
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : [NSNull null]]);
|
|
}]];
|
|
}
|
|
|
|
// 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 *tmpDirFullPath = [self getTmpDirectory];
|
|
NSString *filePath = [tmpDirFullPath 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
|