123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import "RCTImagePickerManager.h"
- #import <FBReactNativeSpec/FBReactNativeSpec.h>
- #import <MobileCoreServices/UTCoreTypes.h>
- #import <UIKit/UIKit.h>
- #import <React/RCTConvert.h>
- #import <React/RCTImageStoreManager.h>
- #import <React/RCTRootView.h>
- #import <React/RCTUtils.h>
- #import "RCTCameraRollPlugins.h"
- @interface RCTImagePickerController : UIImagePickerController
- @property (nonatomic, assign) BOOL unmirrorFrontFacingCamera;
- @end
- @implementation RCTImagePickerController
- @end
- @interface RCTImagePickerManager () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, NativeImagePickerIOSSpec>
- @end
- @implementation RCTImagePickerManager
- {
- NSMutableArray<UIImagePickerController *> *_pickers;
- NSMutableArray<RCTResponseSenderBlock> *_pickerCallbacks;
- NSMutableArray<RCTResponseSenderBlock> *_pickerCancelCallbacks;
- NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *_pendingVideoInfo;
- }
- RCT_EXPORT_MODULE(ImagePickerIOS);
- @synthesize bridge = _bridge;
- - (id)init
- {
- if (self = [super init]) {
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(cameraChanged:)
- name:@"AVCaptureDeviceDidStartRunningNotification"
- object:nil];
- }
- return self;
- }
- + (BOOL)requiresMainQueueSetup
- {
- return NO;
- }
- - (dispatch_queue_t)methodQueue
- {
- return dispatch_get_main_queue();
- }
- RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback)
- {
- NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
- callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]);
- }
- RCT_EXPORT_METHOD(canUseCamera:(RCTResponseSenderBlock)callback)
- {
- callback(@[@([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])]);
- }
- RCT_EXPORT_METHOD(openCameraDialog:(JS::NativeImagePickerIOS::SpecOpenCameraDialogConfig &)config
- successCallback:(RCTResponseSenderBlock)callback
- cancelCallback:(RCTResponseSenderBlock)cancelCallback)
- {
- if (RCTRunningInAppExtension()) {
- cancelCallback(@[@"Camera access is unavailable in an app extension"]);
- return;
- }
- RCTImagePickerController *imagePicker = [RCTImagePickerController new];
- imagePicker.delegate = self;
- imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
- NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
- imagePicker.mediaTypes = availableMediaTypes;
- imagePicker.unmirrorFrontFacingCamera = config.unmirrorFrontFacingCamera() ? YES : NO;
- if (config.videoMode()) {
- imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
- }
- [self _presentPicker:imagePicker
- successCallback:callback
- cancelCallback:cancelCallback];
- }
- RCT_EXPORT_METHOD(openSelectDialog:(JS::NativeImagePickerIOS::SpecOpenSelectDialogConfig &)config
- successCallback:(RCTResponseSenderBlock)callback
- cancelCallback:(RCTResponseSenderBlock)cancelCallback)
- {
- if (RCTRunningInAppExtension()) {
- cancelCallback(@[@"Image picker is currently unavailable in an app extension"]);
- return;
- }
- UIImagePickerController *imagePicker = [UIImagePickerController new];
- imagePicker.delegate = self;
- imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
- NSMutableArray<NSString *> *allowedTypes = [NSMutableArray new];
- if (config.showImages()) {
- [allowedTypes addObject:(NSString *)kUTTypeImage];
- }
- if (config.showVideos()) {
- [allowedTypes addObject:(NSString *)kUTTypeMovie];
- }
- imagePicker.mediaTypes = allowedTypes;
- [self _presentPicker:imagePicker
- successCallback:callback
- cancelCallback:cancelCallback];
- }
- // In iOS 13, the URLs provided when selecting videos from the library are only valid while the
- // info object provided by the delegate is retained.
- // This method provides a way to clear out all retained pending info objects.
- RCT_EXPORT_METHOD(clearAllPendingVideos)
- {
- [_pendingVideoInfo removeAllObjects];
- _pendingVideoInfo = [NSMutableDictionary new];
- }
- // In iOS 13, the URLs provided when selecting videos from the library are only valid while the
- // info object provided by the delegate is retained.
- // This method provides a way to release the info object for a particular file url when the application
- // is done with it, for example after the video has been uploaded or copied locally.
- RCT_EXPORT_METHOD(removePendingVideo:(NSString *)url)
- {
- [_pendingVideoInfo removeObjectForKey:url];
- }
- - (void)imagePickerController:(UIImagePickerController *)picker
- didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
- {
- NSString *mediaType = info[UIImagePickerControllerMediaType];
- BOOL isMovie = [mediaType isEqualToString:(NSString *)kUTTypeMovie];
- NSString *key = isMovie ? UIImagePickerControllerMediaURL : UIImagePickerControllerReferenceURL;
- NSURL *imageURL = info[key];
- UIImage *image = info[UIImagePickerControllerOriginalImage];
- NSNumber *width = 0;
- NSNumber *height = 0;
- if (image) {
- height = @(image.size.height);
- width = @(image.size.width);
- }
- if (imageURL) {
- NSString *imageURLString = imageURL.absoluteString;
- // In iOS 13, video URLs are only valid while info dictionary is retained
- if (@available(iOS 13.0, *)) {
- if (isMovie) {
- _pendingVideoInfo[imageURLString] = info;
- }
- }
- [self _dismissPicker:picker args:@[imageURLString, RCTNullIfNil(height), RCTNullIfNil(width)]];
- return;
- }
- // This is a newly taken image, and doesn't have a URL yet.
- // We need to save it to the image store first.
- UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
- // WARNING: Using ImageStoreManager may cause a memory leak because the
- // image isn't automatically removed from store once we're done using it.
- [_bridge.imageStoreManager storeImage:originalImage withBlock:^(NSString *tempImageTag) {
- [self _dismissPicker:picker args:tempImageTag ? @[tempImageTag, RCTNullIfNil(height), RCTNullIfNil(width)] : nil];
- }];
- }
- - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
- {
- [self _dismissPicker:picker args:nil];
- }
- - (void)_presentPicker:(UIImagePickerController *)imagePicker
- successCallback:(RCTResponseSenderBlock)callback
- cancelCallback:(RCTResponseSenderBlock)cancelCallback
- {
- if (!_pickers) {
- _pickers = [NSMutableArray new];
- _pickerCallbacks = [NSMutableArray new];
- _pickerCancelCallbacks = [NSMutableArray new];
- _pendingVideoInfo = [NSMutableDictionary new];
- }
- [_pickers addObject:imagePicker];
- [_pickerCallbacks addObject:callback];
- [_pickerCancelCallbacks addObject:cancelCallback];
- UIViewController *rootViewController = RCTPresentedViewController();
- [rootViewController presentViewController:imagePicker animated:YES completion:nil];
- }
- - (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args
- {
- NSUInteger index = [_pickers indexOfObject:picker];
- if (index == NSNotFound) {
- // This happens if the user selects multiple items in succession.
- return;
- }
- RCTResponseSenderBlock successCallback = _pickerCallbacks[index];
- RCTResponseSenderBlock cancelCallback = _pickerCancelCallbacks[index];
- [_pickers removeObjectAtIndex:index];
- [_pickerCallbacks removeObjectAtIndex:index];
- [_pickerCancelCallbacks removeObjectAtIndex:index];
- UIViewController *rootViewController = RCTPresentedViewController();
- [rootViewController dismissViewControllerAnimated:YES completion:nil];
- if (args) {
- successCallback(args);
- } else {
- cancelCallback(@[]);
- }
- }
- - (void)cameraChanged:(NSNotification *)notification
- {
- for (UIImagePickerController *picker in _pickers) {
- if (picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
- continue;
- }
- if ([picker isKindOfClass:[RCTImagePickerController class]]
- && ((RCTImagePickerController *)picker).unmirrorFrontFacingCamera
- && picker.cameraDevice == UIImagePickerControllerCameraDeviceFront) {
- picker.cameraViewTransform = CGAffineTransformScale(CGAffineTransformIdentity, -1, 1);
- } else {
- picker.cameraViewTransform = CGAffineTransformIdentity;
- }
- }
- }
- - (std::shared_ptr<facebook::react::TurboModule>)
- getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
- nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
- perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
- {
- return std::make_shared<facebook::react::NativeImagePickerIOSSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
- }
- @end
- Class RCTImagePickerManagerCls(void) {
- return RCTImagePickerManager.class;
- }
|