RCTImagePickerManager.mm 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTImagePickerManager.h"
  8. #import <FBReactNativeSpec/FBReactNativeSpec.h>
  9. #import <MobileCoreServices/UTCoreTypes.h>
  10. #import <UIKit/UIKit.h>
  11. #import <React/RCTConvert.h>
  12. #import <React/RCTImageStoreManager.h>
  13. #import <React/RCTRootView.h>
  14. #import <React/RCTUtils.h>
  15. #import "RCTCameraRollPlugins.h"
  16. @interface RCTImagePickerController : UIImagePickerController
  17. @property (nonatomic, assign) BOOL unmirrorFrontFacingCamera;
  18. @end
  19. @implementation RCTImagePickerController
  20. @end
  21. @interface RCTImagePickerManager () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, NativeImagePickerIOSSpec>
  22. @end
  23. @implementation RCTImagePickerManager
  24. {
  25. NSMutableArray<UIImagePickerController *> *_pickers;
  26. NSMutableArray<RCTResponseSenderBlock> *_pickerCallbacks;
  27. NSMutableArray<RCTResponseSenderBlock> *_pickerCancelCallbacks;
  28. NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *_pendingVideoInfo;
  29. }
  30. RCT_EXPORT_MODULE(ImagePickerIOS);
  31. @synthesize bridge = _bridge;
  32. - (id)init
  33. {
  34. if (self = [super init]) {
  35. [[NSNotificationCenter defaultCenter] addObserver:self
  36. selector:@selector(cameraChanged:)
  37. name:@"AVCaptureDeviceDidStartRunningNotification"
  38. object:nil];
  39. }
  40. return self;
  41. }
  42. + (BOOL)requiresMainQueueSetup
  43. {
  44. return NO;
  45. }
  46. - (dispatch_queue_t)methodQueue
  47. {
  48. return dispatch_get_main_queue();
  49. }
  50. RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback)
  51. {
  52. NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
  53. callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]);
  54. }
  55. RCT_EXPORT_METHOD(canUseCamera:(RCTResponseSenderBlock)callback)
  56. {
  57. callback(@[@([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])]);
  58. }
  59. RCT_EXPORT_METHOD(openCameraDialog:(JS::NativeImagePickerIOS::SpecOpenCameraDialogConfig &)config
  60. successCallback:(RCTResponseSenderBlock)callback
  61. cancelCallback:(RCTResponseSenderBlock)cancelCallback)
  62. {
  63. if (RCTRunningInAppExtension()) {
  64. cancelCallback(@[@"Camera access is unavailable in an app extension"]);
  65. return;
  66. }
  67. RCTImagePickerController *imagePicker = [RCTImagePickerController new];
  68. imagePicker.delegate = self;
  69. imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
  70. NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
  71. imagePicker.mediaTypes = availableMediaTypes;
  72. imagePicker.unmirrorFrontFacingCamera = config.unmirrorFrontFacingCamera() ? YES : NO;
  73. if (config.videoMode()) {
  74. imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
  75. }
  76. [self _presentPicker:imagePicker
  77. successCallback:callback
  78. cancelCallback:cancelCallback];
  79. }
  80. RCT_EXPORT_METHOD(openSelectDialog:(JS::NativeImagePickerIOS::SpecOpenSelectDialogConfig &)config
  81. successCallback:(RCTResponseSenderBlock)callback
  82. cancelCallback:(RCTResponseSenderBlock)cancelCallback)
  83. {
  84. if (RCTRunningInAppExtension()) {
  85. cancelCallback(@[@"Image picker is currently unavailable in an app extension"]);
  86. return;
  87. }
  88. UIImagePickerController *imagePicker = [UIImagePickerController new];
  89. imagePicker.delegate = self;
  90. imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  91. NSMutableArray<NSString *> *allowedTypes = [NSMutableArray new];
  92. if (config.showImages()) {
  93. [allowedTypes addObject:(NSString *)kUTTypeImage];
  94. }
  95. if (config.showVideos()) {
  96. [allowedTypes addObject:(NSString *)kUTTypeMovie];
  97. }
  98. imagePicker.mediaTypes = allowedTypes;
  99. [self _presentPicker:imagePicker
  100. successCallback:callback
  101. cancelCallback:cancelCallback];
  102. }
  103. // In iOS 13, the URLs provided when selecting videos from the library are only valid while the
  104. // info object provided by the delegate is retained.
  105. // This method provides a way to clear out all retained pending info objects.
  106. RCT_EXPORT_METHOD(clearAllPendingVideos)
  107. {
  108. [_pendingVideoInfo removeAllObjects];
  109. _pendingVideoInfo = [NSMutableDictionary new];
  110. }
  111. // In iOS 13, the URLs provided when selecting videos from the library are only valid while the
  112. // info object provided by the delegate is retained.
  113. // This method provides a way to release the info object for a particular file url when the application
  114. // is done with it, for example after the video has been uploaded or copied locally.
  115. RCT_EXPORT_METHOD(removePendingVideo:(NSString *)url)
  116. {
  117. [_pendingVideoInfo removeObjectForKey:url];
  118. }
  119. - (void)imagePickerController:(UIImagePickerController *)picker
  120. didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
  121. {
  122. NSString *mediaType = info[UIImagePickerControllerMediaType];
  123. BOOL isMovie = [mediaType isEqualToString:(NSString *)kUTTypeMovie];
  124. NSString *key = isMovie ? UIImagePickerControllerMediaURL : UIImagePickerControllerReferenceURL;
  125. NSURL *imageURL = info[key];
  126. UIImage *image = info[UIImagePickerControllerOriginalImage];
  127. NSNumber *width = 0;
  128. NSNumber *height = 0;
  129. if (image) {
  130. height = @(image.size.height);
  131. width = @(image.size.width);
  132. }
  133. if (imageURL) {
  134. NSString *imageURLString = imageURL.absoluteString;
  135. // In iOS 13, video URLs are only valid while info dictionary is retained
  136. if (@available(iOS 13.0, *)) {
  137. if (isMovie) {
  138. _pendingVideoInfo[imageURLString] = info;
  139. }
  140. }
  141. [self _dismissPicker:picker args:@[imageURLString, RCTNullIfNil(height), RCTNullIfNil(width)]];
  142. return;
  143. }
  144. // This is a newly taken image, and doesn't have a URL yet.
  145. // We need to save it to the image store first.
  146. UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
  147. // WARNING: Using ImageStoreManager may cause a memory leak because the
  148. // image isn't automatically removed from store once we're done using it.
  149. [_bridge.imageStoreManager storeImage:originalImage withBlock:^(NSString *tempImageTag) {
  150. [self _dismissPicker:picker args:tempImageTag ? @[tempImageTag, RCTNullIfNil(height), RCTNullIfNil(width)] : nil];
  151. }];
  152. }
  153. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
  154. {
  155. [self _dismissPicker:picker args:nil];
  156. }
  157. - (void)_presentPicker:(UIImagePickerController *)imagePicker
  158. successCallback:(RCTResponseSenderBlock)callback
  159. cancelCallback:(RCTResponseSenderBlock)cancelCallback
  160. {
  161. if (!_pickers) {
  162. _pickers = [NSMutableArray new];
  163. _pickerCallbacks = [NSMutableArray new];
  164. _pickerCancelCallbacks = [NSMutableArray new];
  165. _pendingVideoInfo = [NSMutableDictionary new];
  166. }
  167. [_pickers addObject:imagePicker];
  168. [_pickerCallbacks addObject:callback];
  169. [_pickerCancelCallbacks addObject:cancelCallback];
  170. UIViewController *rootViewController = RCTPresentedViewController();
  171. [rootViewController presentViewController:imagePicker animated:YES completion:nil];
  172. }
  173. - (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args
  174. {
  175. NSUInteger index = [_pickers indexOfObject:picker];
  176. if (index == NSNotFound) {
  177. // This happens if the user selects multiple items in succession.
  178. return;
  179. }
  180. RCTResponseSenderBlock successCallback = _pickerCallbacks[index];
  181. RCTResponseSenderBlock cancelCallback = _pickerCancelCallbacks[index];
  182. [_pickers removeObjectAtIndex:index];
  183. [_pickerCallbacks removeObjectAtIndex:index];
  184. [_pickerCancelCallbacks removeObjectAtIndex:index];
  185. UIViewController *rootViewController = RCTPresentedViewController();
  186. [rootViewController dismissViewControllerAnimated:YES completion:nil];
  187. if (args) {
  188. successCallback(args);
  189. } else {
  190. cancelCallback(@[]);
  191. }
  192. }
  193. - (void)cameraChanged:(NSNotification *)notification
  194. {
  195. for (UIImagePickerController *picker in _pickers) {
  196. if (picker.sourceType != UIImagePickerControllerSourceTypeCamera) {
  197. continue;
  198. }
  199. if ([picker isKindOfClass:[RCTImagePickerController class]]
  200. && ((RCTImagePickerController *)picker).unmirrorFrontFacingCamera
  201. && picker.cameraDevice == UIImagePickerControllerCameraDeviceFront) {
  202. picker.cameraViewTransform = CGAffineTransformScale(CGAffineTransformIdentity, -1, 1);
  203. } else {
  204. picker.cameraViewTransform = CGAffineTransformIdentity;
  205. }
  206. }
  207. }
  208. - (std::shared_ptr<facebook::react::TurboModule>)
  209. getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
  210. nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
  211. perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
  212. {
  213. return std::make_shared<facebook::react::NativeImagePickerIOSSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
  214. }
  215. @end
  216. Class RCTImagePickerManagerCls(void) {
  217. return RCTImagePickerManager.class;
  218. }