Skip to content

Commit 6376951

Browse files
fatalsunfacebook-github-bot
authored andcommitted
Fix selecting videos from library in iOS 13
Summary: In iOS 13, Apple made a change that results in video URLs returned by UIImagePickerController becoming invalidated as soon as the info object from the delegate callback is released. This commit works around this issue by retaining these info objects by default and giving the application a way to release them once it is done processing the video. See also https://stackoverflow.com/questions/57798968/didfinishpickingmediawithinfo-returns-different-url-in-ios-13 Reviewed By: olegbl, mmmulani Differential Revision: D17845889 fbshipit-source-id: 12d0e496508dafa2581ef12730f7537ef98c60e2
1 parent 082f293 commit 6376951

File tree

5 files changed

+67
-1
lines changed

5 files changed

+67
-1
lines changed

Libraries/CameraRoll/RCTImagePickerManager.mm

+29-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ @implementation RCTImagePickerManager
3737
NSMutableArray<UIImagePickerController *> *_pickers;
3838
NSMutableArray<RCTResponseSenderBlock> *_pickerCallbacks;
3939
NSMutableArray<RCTResponseSenderBlock> *_pickerCancelCallbacks;
40+
NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *_pendingVideoInfo;
4041
}
4142

4243
RCT_EXPORT_MODULE(ImagePickerIOS);
@@ -133,6 +134,24 @@ - (dispatch_queue_t)methodQueue
133134
cancelCallback:cancelCallback];
134135
}
135136

137+
// In iOS 13, the URLs provided when selecting videos from the library are only valid while the
138+
// info object provided by the delegate is retained.
139+
// This method provides a way to clear out all retained pending info objects.
140+
RCT_EXPORT_METHOD(clearAllPendingVideos)
141+
{
142+
[_pendingVideoInfo removeAllObjects];
143+
_pendingVideoInfo = [NSMutableDictionary new];
144+
}
145+
146+
// In iOS 13, the URLs provided when selecting videos from the library are only valid while the
147+
// info object provided by the delegate is retained.
148+
// This method provides a way to release the info object for a particular file url when the application
149+
// is done with it, for example after the video has been uploaded or copied locally.
150+
RCT_EXPORT_METHOD(removePendingVideo:(NSString *)url)
151+
{
152+
[_pendingVideoInfo removeObjectForKey:url];
153+
}
154+
136155
- (void)imagePickerController:(UIImagePickerController *)picker
137156
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
138157
{
@@ -148,7 +167,15 @@ - (void)imagePickerController:(UIImagePickerController *)picker
148167
width = @(image.size.width);
149168
}
150169
if (imageURL) {
151-
[self _dismissPicker:picker args:@[imageURL.absoluteString, RCTNullIfNil(height), RCTNullIfNil(width)]];
170+
NSString *imageURLString = imageURL.absoluteString;
171+
// In iOS 13, video URLs are only valid while info dictionary is retained
172+
if (@available(iOS 13.0, *)) {
173+
if (isMovie) {
174+
_pendingVideoInfo[imageURLString] = info;
175+
}
176+
}
177+
178+
[self _dismissPicker:picker args:@[imageURLString, RCTNullIfNil(height), RCTNullIfNil(width)]];
152179
return;
153180
}
154181

@@ -176,6 +203,7 @@ - (void)_presentPicker:(UIImagePickerController *)imagePicker
176203
_pickers = [NSMutableArray new];
177204
_pickerCallbacks = [NSMutableArray new];
178205
_pickerCancelCallbacks = [NSMutableArray new];
206+
_pendingVideoInfo = [NSMutableDictionary new];
179207
}
180208

181209
[_pickers addObject:imagePicker];

Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm

+14
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,14 @@ + (RCTManagedPointer *)JS_NativeImagePickerIOS_SpecOpenSelectDialogConfig:(id)js
12381238
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "openSelectDialog", @selector(openSelectDialog:successCallback:cancelCallback:), args, count);
12391239
}
12401240

1241+
static facebook::jsi::Value __hostFunction_NativeImagePickerIOSSpecJSI_clearAllPendingVideos(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
1242+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "clearAllPendingVideos", @selector(clearAllPendingVideos), args, count);
1243+
}
1244+
1245+
static facebook::jsi::Value __hostFunction_NativeImagePickerIOSSpecJSI_removePendingVideo(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
1246+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "removePendingVideo", @selector(removePendingVideo:), args, count);
1247+
}
1248+
12411249

12421250
NativeImagePickerIOSSpecJSI::NativeImagePickerIOSSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<CallInvoker> jsInvoker)
12431251
: ObjCTurboModule("ImagePickerIOS", instance, jsInvoker) {
@@ -1256,6 +1264,12 @@ + (RCTManagedPointer *)JS_NativeImagePickerIOS_SpecOpenSelectDialogConfig:(id)js
12561264

12571265
setMethodArgConversionSelector(@"openSelectDialog", 0, @"JS_NativeImagePickerIOS_SpecOpenSelectDialogConfig:");
12581266

1267+
methodMap_["clearAllPendingVideos"] = MethodMetadata {0, __hostFunction_NativeImagePickerIOSSpecJSI_clearAllPendingVideos};
1268+
1269+
1270+
methodMap_["removePendingVideo"] = MethodMetadata {1, __hostFunction_NativeImagePickerIOSSpecJSI_removePendingVideo};
1271+
1272+
12591273

12601274
}
12611275

Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h

+2
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,8 @@ namespace JS {
12181218
- (void)openSelectDialog:(JS::NativeImagePickerIOS::SpecOpenSelectDialogConfig &)config
12191219
successCallback:(RCTResponseSenderBlock)successCallback
12201220
cancelCallback:(RCTResponseSenderBlock)cancelCallback;
1221+
- (void)clearAllPendingVideos;
1222+
- (void)removePendingVideo:(NSString *)url;
12211223

12221224
@end
12231225
namespace facebook {

Libraries/Image/ImagePickerIOS.js

+20
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ const ImagePickerIOS = {
8080
cancelCallback,
8181
);
8282
},
83+
/**
84+
* In iOS 13, the video URLs returned by the Image Picker are invalidated when
85+
* the picker is dismissed, unless reference to it is held. This API allows
86+
* the application to signal when it's finished with the video so that the
87+
* reference can be cleaned up.
88+
* It is safe to call this method for urlsthat aren't video URLs;
89+
* it will be a no-op.
90+
*/
91+
removePendingVideo: function(url: string): void {
92+
invariant(NativeImagePickerIOS, 'ImagePickerIOS is not available');
93+
NativeImagePickerIOS.removePendingVideo(url);
94+
},
95+
/**
96+
* WARNING: In most cases, removePendingVideo should be used instead because
97+
* clearAllPendingVideos could clear out pending videos made by other callers.
98+
*/
99+
clearAllPendingVideos: function(): void {
100+
invariant(NativeImagePickerIOS, 'ImagePickerIOS is not available');
101+
NativeImagePickerIOS.clearAllPendingVideos();
102+
},
83103
};
84104

85105
module.exports = ImagePickerIOS;

Libraries/Image/NativeImagePickerIOS.js

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export interface Spec extends TurboModule {
3333
successCallback: (imageURL: string, height: number, width: number) => void,
3434
cancelCallback: () => void,
3535
) => void;
36+
+clearAllPendingVideos: () => void;
37+
+removePendingVideo: (url: string) => void;
3638
}
3739

3840
export default (TurboModuleRegistry.get<Spec>('ImagePickerIOS'): ?Spec);

0 commit comments

Comments
 (0)