diff --git a/node_modules/react-native-image-picker/ios/ImagePickerManager.mm b/node_modules/react-native-image-picker/ios/ImagePickerManager.mm index 93e99be..94082ec 100644 --- a/node_modules/react-native-image-picker/ios/ImagePickerManager.mm +++ b/node_modules/react-native-image-picker/ios/ImagePickerManager.mm @@ -68,7 +68,7 @@ RCT_EXPORT_METHOD(launchImageLibrary:(NSDictionary *)options callback:(RCTRespon self.callback = callback; if (target == camera && [ImagePickerUtils isSimulator]) { - self.callback(@[@{@"errorCode": errCameraUnavailable}]); + [self invokeCallback:@[@{@"errorCode": errCameraUnavailable}]]; return; } @@ -87,7 +87,7 @@ RCT_EXPORT_METHOD(launchImageLibrary:(NSDictionary *)options callback:(RCTRespon [self checkPhotosPermissions:^(BOOL granted) { if (!granted) { - self.callback(@[@{@"errorCode": errPermission}]); + [self invokeCallback:@[@{@"errorCode": errPermission}]]; return; } [self showPickerViewController:picker]; @@ -108,7 +108,7 @@ RCT_EXPORT_METHOD(launchImageLibrary:(NSDictionary *)options callback:(RCTRespon if([self.options[@"includeExtra"] boolValue]) { [self checkPhotosPermissions:^(BOOL granted) { if (!granted) { - self.callback(@[@{@"errorCode": errPermission}]); + [self invokeCallback:@[@{@"errorCode": errPermission}]]; return; } [self showPickerViewController:picker]; @@ -126,6 +126,22 @@ RCT_EXPORT_METHOD(launchImageLibrary:(NSDictionary *)options callback:(RCTRespon }); } +// The bridge callback aborts the app if invoked more than once. Delegate +// callbacks can double-fire (e.g. presentationControllerDidDismiss followed by +// picker:didFinishPicking:), so consume the callback exactly once, serialized +// on the main queue. +- (void)invokeCallback:(NSArray *)response +{ + dispatch_async(dispatch_get_main_queue(), ^{ + RCTResponseSenderBlock callback = self.callback; + if (callback == nil) { + return; + } + self.callback = nil; + callback(response); + }); +} + #pragma mark - Helpers NSData* extractImageData(UIImage* image){ @@ -465,7 +481,7 @@ CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIIma if (videoAsset == nil) { NSString *errorMessage = error.localizedFailureReason; if (errorMessage == nil) errorMessage = @"Video asset not found"; - self.callback(@[@{@"errorCode": errOthers, @"errorMessage": errorMessage}]); + [self invokeCallback:@[@{@"errorCode": errOthers, @"errorMessage": errorMessage}]]; return; } [assets addObject:videoAsset]; @@ -473,7 +489,7 @@ CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIIma NSMutableDictionary *response = [[NSMutableDictionary alloc] init]; response[@"assets"] = assets; - self.callback(@[response]); + [self invokeCallback:@[response]]; }; dispatch_async(dispatch_get_main_queue(), ^{ @@ -485,7 +501,7 @@ CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIIma { dispatch_async(dispatch_get_main_queue(), ^{ [picker dismissViewControllerAnimated:YES completion:^{ - self.callback(@[@{@"didCancel": @YES}]); + [self invokeCallback:@[@{@"didCancel": @YES}]]; }]; }); } @@ -496,7 +512,14 @@ CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIIma - (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController { - self.callback(@[@{@"didCancel": @YES}]); + // A selection already being processed must win over the interactive + // dismissal, otherwise the picked assets would later hit a consumed + // callback (or be reported as a cancel). + if (photoSelected == YES) { + return; + } + photoSelected = YES; + [self invokeCallback:@[@{@"didCancel": @YES}]]; } @end @@ -514,9 +537,7 @@ CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIIma photoSelected = YES; if (results.count == 0) { - dispatch_async(dispatch_get_main_queue(), ^{ - self.callback(@[@{@"didCancel": @YES}]); - }); + [self invokeCallback:@[@{@"didCancel": @YES}]]; return; } @@ -571,7 +592,7 @@ CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIIma // mapVideoToAsset can fail and return nil, leaving asset NSNull. for (NSDictionary *asset in assets) { if ([asset isEqual:[NSNull null]]) { - self.callback(@[@{@"errorCode": errOthers}]); + [self invokeCallback:@[@{@"errorCode": errOthers}]]; return; } } @@ -579,7 +600,7 @@ CGImagePropertyOrientation CGImagePropertyOrientationForUIImageOrientation(UIIma NSMutableDictionary *response = [[NSMutableDictionary alloc] init]; [response setObject:assets forKey:@"assets"]; - self.callback(@[response]); + [self invokeCallback:@[response]]; }); }