diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index d7b57c7b7a2a11..4e73ad65c030fb 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -35,6 +35,7 @@ @implementation RCTImagePickerManager NSMutableArray *_pickers; NSMutableArray *_pickerCallbacks; NSMutableArray *_pickerCancelCallbacks; + NSMutableDictionary *> *_pendingVideoInfo; } RCT_EXPORT_MODULE(ImagePickerIOS); @@ -131,6 +132,24 @@ - (dispatch_queue_t)methodQueue 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 *)info { @@ -146,7 +165,15 @@ - (void)imagePickerController:(UIImagePickerController *)picker width = @(image.size.width); } if (imageURL) { - [self _dismissPicker:picker args:@[imageURL.absoluteString, RCTNullIfNil(height), RCTNullIfNil(width)]]; + 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; } @@ -174,6 +201,7 @@ - (void)_presentPicker:(UIImagePickerController *)imagePicker _pickers = [NSMutableArray new]; _pickerCallbacks = [NSMutableArray new]; _pickerCancelCallbacks = [NSMutableArray new]; + _pendingVideoInfo = [NSMutableDictionary new]; } [_pickers addObject:imagePicker]; diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm index eb25fc7d24bcf3..553779b78b1862 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm @@ -1125,6 +1125,14 @@ + (RCTManagedPointer *)JS_NativeImagePickerIOS_SpecOpenSelectDialogConfig:(id)js return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "openSelectDialog", @selector(openSelectDialog:successCallback:cancelCallback:), args, count); } + static facebook::jsi::Value __hostFunction_NativeImagePickerIOSSpecJSI_clearAllPendingVideos(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "clearAllPendingVideos", @selector(clearAllPendingVideos), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeImagePickerIOSSpecJSI_removePendingVideo(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "removePendingVideo", @selector(removePendingVideo:), args, count); + } + NativeImagePickerIOSSpecJSI::NativeImagePickerIOSSpecJSI(id instance, std::shared_ptr jsInvoker) : ObjCTurboModule("ImagePickerIOS", instance, jsInvoker) { @@ -1143,6 +1151,12 @@ + (RCTManagedPointer *)JS_NativeImagePickerIOS_SpecOpenSelectDialogConfig:(id)js setMethodArgConversionSelector(@"openSelectDialog", 0, @"JS_NativeImagePickerIOS_SpecOpenSelectDialogConfig:"); + methodMap_["clearAllPendingVideos"] = MethodMetadata {0, __hostFunction_NativeImagePickerIOSSpecJSI_clearAllPendingVideos}; + + + methodMap_["removePendingVideo"] = MethodMetadata {1, __hostFunction_NativeImagePickerIOSSpecJSI_removePendingVideo}; + + } diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h index eee2be7d6fcb37..617ec6a9683b84 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h @@ -1143,6 +1143,8 @@ namespace JS { - (void)openSelectDialog:(JS::NativeImagePickerIOS::SpecOpenSelectDialogConfig &)config successCallback:(RCTResponseSenderBlock)successCallback cancelCallback:(RCTResponseSenderBlock)cancelCallback; +- (void)clearAllPendingVideos; +- (void)removePendingVideo:(NSString *)url; @end namespace facebook { diff --git a/Libraries/Image/ImagePickerIOS.js b/Libraries/Image/ImagePickerIOS.js index 587d89b6ed6fe8..a7f0049d548a4d 100644 --- a/Libraries/Image/ImagePickerIOS.js +++ b/Libraries/Image/ImagePickerIOS.js @@ -80,6 +80,26 @@ const ImagePickerIOS = { cancelCallback, ); }, + /** + * In iOS 13, the video URLs returned by the Image Picker are invalidated when + * the picker is dismissed, unless reference to it is held. This API allows + * the application to signal when it's finished with the video so that the + * reference can be cleaned up. + * It is safe to call this method for urlsthat aren't video URLs; + * it will be a no-op. + */ + removePendingVideo: function(url: string): void { + invariant(NativeImagePickerIOS, 'ImagePickerIOS is not available'); + NativeImagePickerIOS.removePendingVideo(url); + }, + /** + * WARNING: In most cases, removePendingVideo should be used instead because + * clearAllPendingVideos could clear out pending videos made by other callers. + */ + clearAllPendingVideos: function(): void { + invariant(NativeImagePickerIOS, 'ImagePickerIOS is not available'); + NativeImagePickerIOS.clearAllPendingVideos(); + }, }; module.exports = ImagePickerIOS; diff --git a/Libraries/Image/NativeImagePickerIOS.js b/Libraries/Image/NativeImagePickerIOS.js index 895f95f39f69d1..6101c7b0380748 100644 --- a/Libraries/Image/NativeImagePickerIOS.js +++ b/Libraries/Image/NativeImagePickerIOS.js @@ -33,6 +33,8 @@ export interface Spec extends TurboModule { successCallback: (imageURL: string, height: number, width: number) => void, cancelCallback: () => void, ) => void; + +clearAllPendingVideos: () => void; + +removePendingVideo: (url: string) => void; } export default (TurboModuleRegistry.get('ImagePickerIOS'): ?Spec);