在使用react-native-image-crop-picker(版本0.20.0)组件选择图片的时候,我希望将选取的图片保存到应用内部。
package com.reactnative.ivpusic.imagepicker;
import android.Manifest;
import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.util.Base64;
import android.webkit.MimeTypeMap;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.PromiseImpl;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropActivity;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
class PickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
private static final int IMAGE_PICKER_REQUEST = 61110;
private static final int CAMERA_PICKER_REQUEST = 61111;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_PICKER_CANCELLED_KEY = "E_PICKER_CANCELLED";
private static final String E_PICKER_CANCELLED_MSG = "User cancelled image selection";
private static final String E_CALLBACK_ERROR = "E_CALLBACK_ERROR";
private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
private static final String E_FAILED_TO_OPEN_CAMERA = "E_FAILED_TO_OPEN_CAMERA";
private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";
private static final String E_CAMERA_IS_NOT_AVAILABLE = "E_CAMERA_IS_NOT_AVAILABLE";
private static final String E_CANNOT_LAUNCH_CAMERA = "E_CANNOT_LAUNCH_CAMERA";
private static final String E_PERMISSIONS_MISSING = "E_PERMISSION_MISSING";
private static final String E_ERROR_WHILE_CLEANING_FILES = "E_ERROR_WHILE_CLEANING_FILES";
private String mediaType = "any";
private boolean multiple = false;
private boolean includeBase64 = false;
private boolean includeExif = false;
private boolean cropping = false;
private boolean cropperCircleOverlay = false;
private boolean freeStyleCropEnabled = false;
private boolean showCropGuidelines = true;
private boolean hideBottomControls = false;
private boolean enableRotationGesture = false;
private ReadableMap options;
//Grey 800
private final String DEFAULT_TINT = "#424242";
private String cropperActiveWidgetColor = DEFAULT_TINT;
private String cropperStatusBarColor = DEFAULT_TINT;
private String cropperToolbarColor = DEFAULT_TINT;
private String cropperToolbarTitle = null;
//Light Blue 500
private final String DEFAULT_WIDGET_COLOR = "#03A9F4";
private int width = 200;
private int height = 200;
private Uri mCameraCaptureURI;
private String mCurrentPhotoPath;
private ResultCollector resultCollector;
private Compression compression = new Compression();
private ReactApplicationContext reactContext = null;
PickerModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(this);
this.reactContext = reactContext;
}
private String getTmpDir(Activity activity) {
String tmpDir = activity.getCacheDir() + "/react-native-image-crop-picker";
Boolean created = new File(tmpDir).mkdir();
return tmpDir;
}
@Override
public String getName() {
return "ImageCropPicker";
}
private void setConfiguration(final ReadableMap options) {
mediaType = options.hasKey("mediaType") ? options.getString("mediaType") : mediaType;
multiple = options.hasKey("multiple") && options.getBoolean("multiple");
includeBase64 = options.hasKey("includeBase64") && options.getBoolean("includeBase64");
includeExif = options.hasKey("includeExif") && options.getBoolean("includeExif");
width = options.hasKey("width") ? options.getInt("width") : width;
height = options.hasKey("height") ? options.getInt("height") : height;
cropping = options.hasKey("cropping") ? options.getBoolean("cropping") : cropping;
cropperActiveWidgetColor = options.hasKey("cropperActiveWidgetColor") ? options.getString("cropperActiveWidgetColor") : cropperActiveWidgetColor;
cropperStatusBarColor = options.hasKey("cropperStatusBarColor") ? options.getString("cropperStatusBarColor") : cropperStatusBarColor;
cropperToolbarColor = options.hasKey("cropperToolbarColor") ? options.getString("cropperToolbarColor") : cropperToolbarColor;
cropperToolbarTitle = options.hasKey("cropperToolbarTitle") ? options.getString("cropperToolbarTitle") : null;
cropperCircleOverlay = options.hasKey("cropperCircleOverlay") ? options.getBoolean("cropperCircleOverlay") : cropperCircleOverlay;
freeStyleCropEnabled = options.hasKey("freeStyleCropEnabled") ? options.getBoolean("freeStyleCropEnabled") : freeStyleCropEnabled;
showCropGuidelines = options.hasKey("showCropGuidelines") ? options.getBoolean("showCropGuidelines") : showCropGuidelines;
hideBottomControls = options.hasKey("hideBottomControls") ? options.getBoolean("hideBottomControls") : hideBottomControls;
enableRotationGesture = options.hasKey("enableRotationGesture") ? options.getBoolean("enableRotationGesture") : enableRotationGesture;
this.options = options;
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
}
}
fileOrDirectory.delete();
}
@ReactMethod
public void clean(final Promise promise) {
final Activity activity = getCurrentActivity();
final PickerModule module = this;
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
permissionsCheck(activity, promise, Arrays.asList(Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
File file = new File(module.getTmpDir(activity));
if (!file.exists()) throw new Exception("File does not exist");
module.deleteRecursive(file);
promise.resolve(null);
} catch (Exception ex) {
ex.printStackTrace();
promise.reject(E_ERROR_WHILE_CLEANING_FILES, ex.getMessage());
}
return null;
}
});
}
@ReactMethod
public void cleanSingle(final String pathToDelete, final Promise promise) {
if (pathToDelete == null) {
promise.reject(E_ERROR_WHILE_CLEANING_FILES, "Cannot cleanup empty path");
return;
}
final Activity activity = getCurrentActivity();
final PickerModule module = this;
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
permissionsCheck(activity, promise, Arrays.asList(Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
String path = pathToDelete;
final String filePrefix = "file://";
if (path.startsWith(filePrefix)) {
path = path.substring(filePrefix.length());
}
File file = new File(path);
if (!file.exists()) throw new Exception("File does not exist. Path: " + path);
module.deleteRecursive(file);
promise.resolve(null);
} catch (Exception ex) {
ex.printStackTrace();
promise.reject(E_ERROR_WHILE_CLEANING_FILES, ex.getMessage());
}
return null;
}
});
}
private void permissionsCheck(final Activity activity, final Promise promise, final List<String> requiredPermissions, final Callable<Void> callback) {
List<String> missingPermissions = new ArrayList<>();
for (String permission : requiredPermissions) {
int status = ActivityCompat.checkSelfPermission(activity, permission);
if (status != PackageManager.PERMISSION_GRANTED) {
missingPermissions.add(permission);
}
}
if (!missingPermissions.isEmpty()) {
((PermissionAwareActivity) activity).requestPermissions(missingPermissions.toArray(new String[missingPermissions.size()]), 1, new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == 1) {
for (int grantResult : grantResults) {
if (grantResult == PackageManager.PERMISSION_DENIED) {
promise.reject(E_PERMISSIONS_MISSING, "Required permission missing");
return true;
}
}
try {
callback.call();
} catch (Exception e) {
promise.reject(E_CALLBACK_ERROR, "Unknown error", e);
}
}
return true;
}
});
return;
}
// all permissions granted
try {
callback.call();
} catch (Exception e) {
promise.reject(E_CALLBACK_ERROR, "Unknown error", e);
}
}
@ReactMethod
public void openCamera(final ReadableMap options, final Promise promise) {
final Activity activity = getCurrentActivity();
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
if (!isCameraAvailable(activity)) {
promise.reject(E_CAMERA_IS_NOT_AVAILABLE, "Camera not available");
return;
}
setConfiguration(options);
resultCollector = new ResultCollector(promise, multiple);
permissionsCheck(activity, promise, Arrays.asList(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() throws Exception {
initiateCamera(activity);
return null;
}
});
}
private void initiateCamera(Activity activity) {
try {
int requestCode = CAMERA_PICKER_REQUEST;
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File imageFile = createImageFile();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mCameraCaptureURI = Uri.fromFile(imageFile);
} else {
mCameraCaptureURI = FileProvider.getUriForFile(activity,
activity.getApplicationContext().getPackageName() + ".provider",
imageFile);
}
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraCaptureURI);
if (cameraIntent.resolveActivity(activity.getPackageManager()) == null) {
resultCollector.notifyProblem(E_CANNOT_LAUNCH_CAMERA, "Cannot launch camera");
return;
}
activity.startActivityForResult(cameraIntent, requestCode);
} catch (Exception e) {
resultCollector.notifyProblem(E_FAILED_TO_OPEN_CAMERA, e);
}
}
private void initiatePicker(final Activity activity) {
try {
final Intent galleryIntent = new Intent(Intent.ACTION_PICK);
if (cropping || mediaType.equals("photo")) {
galleryIntent.setType("image/*");
} else if (mediaType.equals("video")) {
galleryIntent.setType("video/*");
} else {
galleryIntent.setType("*/*");
String[] mimetypes = {"image/*", "video/*"};
galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes);
}
galleryIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
galleryIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");
activity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST);
} catch (Exception e) {
resultCollector.notifyProblem(E_FAILED_TO_SHOW_PICKER, e);
}
}
@ReactMethod
public void openPicker(final ReadableMap options, final Promise promise) {
final Activity activity = getCurrentActivity();
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
setConfiguration(options);
resultCollector = new ResultCollector(promise, multiple);
permissionsCheck(activity, promise, Collections.singletonList(Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() throws Exception {
initiatePicker(activity);
return null;
}
});
}
@ReactMethod
public void openCropper(final ReadableMap options, final Promise promise) {
final Activity activity = getCurrentActivity();
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
setConfiguration(options);
resultCollector = new ResultCollector(promise, false);
Uri uri = Uri.parse(options.getString("path"));
startCropping(activity, uri);
}
private String getBase64StringFromFile(String absoluteFilePath) {
InputStream inputStream;
try {
inputStream = new FileInputStream(new File(absoluteFilePath));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
byte[] bytes;
byte[] buffer = new byte[8192];
int bytesRead;
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
while ((bytesRead = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
bytes = output.toByteArray();
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
private static String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}
private WritableMap getSelection(Activity activity, Uri uri, boolean isCamera) throws Exception {
String path = resolveRealPath(activity, uri, isCamera);
if (path == null || path.isEmpty()) {
throw new Exception("Cannot resolve asset path.");
}
return getImage(activity, path);
}
private void getAsyncSelection(final Activity activity, Uri uri, boolean isCamera) throws Exception {
//String path = resolveRealPath(activity, uri, isCamera);
File file = createFileFromURI(uri);
String path = file.getAbsolutePath();
//File imageFile = new File(uri.getPath());
if (path == null || path.isEmpty()) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot resolve asset path.");
return;
}
String mime = getMimeType(path);
if (mime != null && mime.startsWith("video/")) {
getVideo(activity, path, mime);
return;
}
resultCollector.notifySuccess(getImage(activity, path));
}
private File createFileFromURI(Uri uri) throws Exception {
File imgPath = reactContext.getExternalCacheDir();
File path2 = new File(imgPath.getAbsolutePath().replace("cache","images"));
if (!path2.exists() && !path2.isDirectory()) {
path2.mkdirs();
}
String str = uri.getLastPathSegment();
str = str.substring(str.indexOf("."));
File file = new File(path2.getAbsolutePath(), UUID.randomUUID().toString() + str);
InputStream input = reactContext.getContentResolver().openInputStream(uri);
OutputStream output = new FileOutputStream(file);
try {
byte[] buffer = new byte[4 * 1024];
int read;
while ((read = input.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
output.flush();
} finally {
output.close();
input.close();
}
return file;
}
private Bitmap validateVideo(String path) throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
Bitmap bmp = retriever.getFrameAtTime();
if (bmp == null) {
throw new Exception("Cannot retrieve video data");
}
return bmp;
}
private void getVideo(final Activity activity, final String path, final String mime) throws Exception {
validateVideo(path);
final String compressedVideoPath = getTmpDir(activity) + "/" + UUID.randomUUID().toString() + ".mp4";
new Thread(new Runnable() {
@Override
public void run() {
compression.compressVideo(activity, options, path, compressedVideoPath, new PromiseImpl(new Callback() {
@Override
public void invoke(Object... args) {
String videoPath = (String) args[0];
try {
Bitmap bmp = validateVideo(videoPath);
long modificationDate = new File(videoPath).lastModified();
WritableMap video = new WritableNativeMap();
video.putInt("width", bmp.getWidth());
video.putInt("height", bmp.getHeight());
video.putString("mime", mime);
video.putInt("size", (int) new File(videoPath).length());
video.putString("path", "file://" + videoPath);
video.putString("modificationDate", String.valueOf(modificationDate));
resultCollector.notifySuccess(video);
} catch (Exception e) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, e);
}
}
}, new Callback() {
@Override
public void invoke(Object... args) {
WritableNativeMap ex = (WritableNativeMap) args[0];
resultCollector.notifyProblem(ex.getString("code"), ex.getString("message"));
}
}));
}
}).run();
}
private String resolveRealPath(Activity activity, Uri uri, boolean isCamera) {
String path;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
path = RealPathUtil.getRealPathFromURI(activity, uri);
} else {
if (isCamera) {
Uri imageUri = Uri.parse(mCurrentPhotoPath);
path = imageUri.getPath();
} else {
path = RealPathUtil.getRealPathFromURI(activity, uri);
}
}
return path;
}
private BitmapFactory.Options validateImage(String path) throws Exception {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inDither = true;
BitmapFactory.decodeFile(path, options);
if (options.outMimeType == null || options.outWidth == 0 || options.outHeight == 0) {
throw new Exception("Invalid image selected");
}
return options;
}
private WritableMap getImage(final Activity activity, String path) throws Exception {
WritableMap image = new WritableNativeMap();
if (path.startsWith("http://") || path.startsWith("https://")) {
throw new Exception("Cannot select remote files");
}
validateImage(path);
// if compression options are provided image will be compressed. If none options is provided,
// then original image will be returned
File compressedImage = compression.compressImage(activity, options, path);
String compressedImagePath = compressedImage.getPath();
BitmapFactory.Options options = validateImage(compressedImagePath);
long modificationDate = new File(path).lastModified();
image.putString("path", "file://" + compressedImagePath);
image.putInt("width", options.outWidth);
image.putInt("height", options.outHeight);
image.putString("mime", options.outMimeType);
image.putInt("size", (int) new File(compressedImagePath).length());
image.putString("modificationDate", String.valueOf(modificationDate));
if (includeBase64) {
image.putString("data", getBase64StringFromFile(compressedImagePath));
}
if (includeExif) {
try {
WritableMap exif = ExifExtractor.extract(path);
image.putMap("exif", exif);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return image;
}
private void configureCropperColors(UCrop.Options options) {
int activeWidgetColor = Color.parseColor(cropperActiveWidgetColor);
int toolbarColor = Color.parseColor(cropperToolbarColor);
int statusBarColor = Color.parseColor(cropperStatusBarColor);
options.setToolbarColor(toolbarColor);
options.setStatusBarColor(statusBarColor);
if (activeWidgetColor == Color.parseColor(DEFAULT_TINT)) {
/*
Default tint is grey => use a more flashy color that stands out more as the call to action
Here we use 'Light Blue 500' from https://material.google.com/style/color.html#color-color-palette
*/
options.setActiveWidgetColor(Color.parseColor(DEFAULT_WIDGET_COLOR));
} else {
//If they pass a custom tint color in, we use this for everything
options.setActiveWidgetColor(activeWidgetColor);
}
}
private void startCropping(Activity activity, Uri uri) {
UCrop.Options options = new UCrop.Options();
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);
options.setCompressionQuality(100);
options.setCircleDimmedLayer(cropperCircleOverlay);
options.setFreeStyleCropEnabled(freeStyleCropEnabled);
options.setShowCropGrid(showCropGuidelines);
options.setHideBottomControls(hideBottomControls);
if (cropperToolbarTitle != null) {
options.setToolbarTitle(cropperToolbarTitle);
}
if (enableRotationGesture) {
// UCropActivity.ALL = enable both rotation & scaling
options.setAllowedGestures(
UCropActivity.ALL, // When 'scale'-tab active
UCropActivity.ALL, // When 'rotate'-tab active
UCropActivity.ALL // When 'aspect ratio'-tab active
);
}
configureCropperColors(options);
UCrop.of(uri, Uri.fromFile(new File(this.getTmpDir(activity), UUID.randomUUID().toString() + ".jpg")))
.withMaxResultSize(width, height)
.withAspectRatio(width, height)
.withOptions(options)
.start(activity);
}
private void imagePickerResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (resultCode == Activity.RESULT_CANCELED) {
resultCollector.notifyProblem(E_PICKER_CANCELLED_KEY, E_PICKER_CANCELLED_MSG);
} else if (resultCode == Activity.RESULT_OK) {
if (multiple) {
ClipData clipData = data.getClipData();
try {
// only one image selected
if (clipData == null) {
resultCollector.setWaitCount(1);
getAsyncSelection(activity, data.getData(), false);
} else {
resultCollector.setWaitCount(clipData.getItemCount());
for (int i = 0; i < clipData.getItemCount(); i++) {
getAsyncSelection(activity, clipData.getItemAt(i).getUri(), false);
}
}
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
} else {
Uri uri = data.getData();
if (uri == null) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url");
return;
}
if (cropping) {
startCropping(activity, uri);
} else {
try {
getAsyncSelection(activity, uri, false);
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
}
}
}
}
private void cameraPickerResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (resultCode == Activity.RESULT_CANCELED) {
resultCollector.notifyProblem(E_PICKER_CANCELLED_KEY, E_PICKER_CANCELLED_MSG);
} else if (resultCode == Activity.RESULT_OK) {
Uri uri = mCameraCaptureURI;
if (uri == null) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url");
return;
}
if (cropping) {
UCrop.Options options = new UCrop.Options();
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);
startCropping(activity, uri);
} else {
try {
resultCollector.setWaitCount(1);
resultCollector.notifySuccess(getSelection(activity, uri, true));
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
}
}
}
private void croppingResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (data != null) {
final Uri resultUri = UCrop.getOutput(data);
if (resultUri != null) {
try {
WritableMap result = getSelection(activity, resultUri, false);
result.putMap("cropRect", PickerModule.getCroppedRectMap(data));
resultCollector.setWaitCount(1);
resultCollector.notifySuccess(result);
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
} else {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot find image data");
}
} else {
resultCollector.notifyProblem(E_PICKER_CANCELLED_KEY, E_PICKER_CANCELLED_MSG);
}
}
@Override
public void onActivityResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (requestCode == IMAGE_PICKER_REQUEST) {
imagePickerResult(activity, requestCode, resultCode, data);
} else if (requestCode == CAMERA_PICKER_REQUEST) {
cameraPickerResult(activity, requestCode, resultCode, data);
} else if (requestCode == UCrop.REQUEST_CROP) {
croppingResult(activity, requestCode, resultCode, data);
}
}
@Override
public void onNewIntent(Intent intent) {
}
private boolean isCameraAvailable(Activity activity) {
return activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
|| activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
}
private File createImageFile() throws IOException {
String imageFileName = "image-" + UUID.randomUUID().toString();
//File file = new File(reactContext.getExternalCacheDir(), "photo-" + uri.getLastPathSegment());
File path = reactContext.getExternalCacheDir();
String filePath = path.getAbsolutePath().replace("cache","images");
// File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
path = new File(filePath);
if (!path.exists() && !path.isDirectory()) {
path.mkdirs();
}
File image = File.createTempFile(imageFileName, ".jpg", path);
//start
String filename = new StringBuilder("image-").append(UUID.randomUUID().toString())
.append(".jpg").toString();
//ReadableMapUtils.hasAndNotNullReadableMap(options, "storageOptions")
//File path2 = reactContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File result = new File(path, filename);
try
{
path.mkdirs();
result.createNewFile();
}
catch (IOException e)
{
e.printStackTrace();
result = null;
}
// Save a file: path for use with ACTION_VIEW intents
//mCurrentPhotoPath = "file:" + image.getAbsolutePath();
mCurrentPhotoPath = "file:" + result.getAbsolutePath();
return result;
}
private static WritableMap getCroppedRectMap(Intent data) {
final int DEFAULT_VALUE = -1;
final WritableMap map = new WritableNativeMap();
map.putInt("x", data.getIntExtra(UCrop.EXTRA_OUTPUT_OFFSET_X, DEFAULT_VALUE));
map.putInt("y", data.getIntExtra(UCrop.EXTRA_OUTPUT_OFFSET_Y, DEFAULT_VALUE));
map.putInt("width", data.getIntExtra(UCrop.EXTRA_OUTPUT_IMAGE_WIDTH, DEFAULT_VALUE));
map.putInt("height", data.getIntExtra(UCrop.EXTRA_OUTPUT_IMAGE_HEIGHT, DEFAULT_VALUE));
return map;
}
}
2.ios端
修改node_modules\react-native-image-crop-picker\ios\src\ImageCropPicker.m文件
修改如下:
//
// 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 @"E_PERMISSION_MISSING"
#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 @"E_NO_IMAGE_DATA_FOUND"
#define ERROR_PICKER_NO_DATA_MSG @"Cannot find image data"
#define ERROR_CROPPER_IMAGE_NOT_FOUND_KEY @"E_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,
@"includeExif": @NO,
@"compressVideo": @YES,
@"minFiles": @1,
@"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;
}
+ (BOOL)requiresMainQueueSetup {
return YES;
}
- (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.currentSelectionMode = CAMERA;
#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)viewDidLoad {
[self viewDidLoad];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = [info objectForKey:UIImagePickerControllerOriginalImage];
UIImage *chosenImageT = [chosenImage fixOrientation];
NSDictionary *exif;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [info objectForKey:UIImagePickerControllerMediaMetadata];
}
[self processSingleImagePick:chosenImageT withExif:exif withViewController:picker withSourceURL:self.croppingFile[@"sourceURL"] withLocalIdentifier:self.croppingFile[@"localIdentifier"] withFilename:self.croppingFile[@"filename"] withCreationDate:self.croppingFile[@"creationDate"] withModificationDate:self.croppingFile[@"modificationDate"]];
}
- (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;
*/
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentPath = [paths lastObject];
NSString *tmpFullPath =[documentPath stringByAppendingString:@"/image/"];
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.currentSelectionMode = PICKER;
[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.minimumNumberOfSelection = abs([[self.options objectForKey:@"minFiles"] intValue]);
imagePickerController.maximumNumberOfSelection = abs([[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.currentSelectionMode = CROPPING;
NSString *path = [options objectForKey:@"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 = NSTextAlignmentCenter;
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]
withExif:nil
withSourceURL:[sourceURL absoluteString]
withLocalIdentifier: forAsset.localIdentifier
withFilename:[forAsset valueForKey:@"filename"]
withWidth:[NSNumber numberWithFloat:track.naturalSize.width]
withHeight:[NSNumber numberWithFloat:track.naturalSize.height]
withMime:@"video/mp4"
withSize:fileSizeValue
withData:nil
withCreationDate:forAsset.creationDate
withModificationDate:forAsset.modificationDate
]);
} else {
completion(nil);
}
}];
}];
}
- (NSDictionary*) createAttachmentResponse:(NSString*)filePath withExif:(NSDictionary*) exif withSourceURL:(NSString*)sourceURL withLocalIdentifier:(NSString*)localIdentifier withFilename:(NSString*)filename withWidth:(NSNumber*)width withHeight:(NSNumber*)height withMime:(NSString*)mime withSize:(NSNumber*)size withData:(NSString*)data withCreationDate:(NSDate*)creationDate withModificationDate:(NSDate*)modificationDate
{
return @{
@"path": filePath,
@"sourceURL": (sourceURL) ? sourceURL : [NSNull null],
@"localIdentifier": (localIdentifier) ? localIdentifier : [NSNull null],
@"filename": (filename) ? filename : [NSNull null],
@"width": width,
@"height": height,
@"mime": mime,
@"size": size,
@"data": (data) ? data : [NSNull null],
@"exif": (exif) ? exif : [NSNull null],
@"creationDate:": (creationDate) ? [NSString stringWithFormat:@"%.0f", [creationDate timeIntervalSince1970]] : [NSNull null],
@"modificationDate": (modificationDate) ? [NSString stringWithFormat:@"%.0f", [modificationDate timeIntervalSince1970]] : [NSNull null],
};
}
- (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) {
NSURL *sourceURL = [info objectForKey:@"PHImageFileURLKey"];
dispatch_async(dispatch_get_main_queue(), ^{
[lock lock];
@autoreleasepool {
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;
}
NSDictionary* exif = nil;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [[CIImage imageWithData:imageData] properties];
}
[selections addObject:[self createAttachmentResponse:filePath
withExif: exif
withSourceURL:[sourceURL absoluteString]
withLocalIdentifier: phAsset.localIdentifier
withFilename: [phAsset valueForKey:@"filename"]
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]: nil
withCreationDate:phAsset.creationDate
withModificationDate:phAsset.modificationDate
]];
}
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) {
NSURL *sourceURL = [info objectForKey:@"PHImageFileURLKey"];
NSDictionary* exif;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [[CIImage imageWithData:imageData] properties];
}
dispatch_async(dispatch_get_main_queue(), ^{
[indicatorView stopAnimating];
[overlayView removeFromSuperview];
[self processSingleImagePick:[UIImage imageWithData:imageData]
withExif: exif
withViewController:imagePickerController
withSourceURL:[sourceURL absoluteString]
withLocalIdentifier:phAsset.localIdentifier
withFilename:[phAsset valueForKey:@"filename"]
withCreationDate:phAsset.creationDate
withModificationDate:phAsset.modificationDate];
});
}];
}
}];
}
}
- (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 withExif:(NSDictionary*) exif withViewController:(UIViewController*)viewController withSourceURL:(NSString*)sourceURL withLocalIdentifier:(NSString*)localIdentifier withFilename:(NSString*)filename withCreationDate:(NSDate*)creationDate withModificationDate:(NSDate*)modificationDate {
if (image == nil) {
[viewController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
self.reject(ERROR_PICKER_NO_DATA_KEY, ERROR_PICKER_NO_DATA_MSG, nil);
}]];
return;
}
NSLog(@"id: %@ filename: %@", localIdentifier, filename);
if ([[[self options] objectForKey:@"cropping"] boolValue]) {
self.croppingFile = [[NSMutableDictionary alloc] init];
self.croppingFile[@"sourceURL"] = sourceURL;
self.croppingFile[@"localIdentifier"] = localIdentifier;
self.croppingFile[@"filename"] = filename;
self.croppingFile[@"creationDate"] = creationDate;
self.croppingFile[@"modifcationDate"] = modificationDate;
NSLog(@"CroppingFile %@", self.croppingFile);
[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
withExif:exif
withSourceURL:sourceURL
withLocalIdentifier:localIdentifier
withFilename:filename
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] : nil
withCreationDate:creationDate
withModificationDate:modificationDate]);
}]];
}
}
#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 selectionDone:NO completion:[self waitAnimationEnd:^{
if (self.currentSelectionMode == CROPPING) {
self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, nil);
}
}]];
}
- (void) dismissCropper:(RSKImageCropViewController*)controller selectionDone:(BOOL)selectionDone completion:(void (^)())completion {
switch (self.currentSelectionMode) {
case CROPPING:
[controller dismissViewControllerAnimated:YES completion:completion];
break;
case PICKER:
if (selectionDone) {
[controller.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:completion];
} else {
// if user opened picker, tried to crop image, and cancelled cropping
// return him to the image selection instead of returning him to the app
[controller.presentingViewController dismissViewControllerAnimated:YES completion:completion];
}
break;
case CAMERA:
[controller.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:completion];
break;
}
}
// 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 selectionDone:YES completion:[self waitAnimationEnd:^{
self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil);
}]];
return;
}
NSDictionary* exif = nil;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [[CIImage imageWithData:imageResult.data] properties];
}
[self dismissCropper:controller selectionDone:YES completion:[self waitAnimationEnd:^{
self.resolve([self createAttachmentResponse:filePath
withExif: exif
withSourceURL: self.croppingFile[@"sourceURL"]
withLocalIdentifier: self.croppingFile[@"localIdentifier"]
withFilename: self.croppingFile[@"filename"]
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] : nil
withCreationDate:self.croppingFile[@"creationDate"]
withModificationDate:self.croppingFile[@"modificationDate"]]);
}]];
}
// 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