-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[image_picker_ios] Image picker camera bug #12185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -533,19 +533,26 @@ - (void)picker:(PHPickerViewController *)picker | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - (void)imagePickerController:(UIImagePickerController *)picker | ||||||||||||||||||||||||||||||||||
| didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info { | ||||||||||||||||||||||||||||||||||
| NSURL *videoURL = info[UIImagePickerControllerMediaURL]; | ||||||||||||||||||||||||||||||||||
| FLTImagePickerMethodCallContext *context = self.callContext; | ||||||||||||||||||||||||||||||||||
| __weak typeof(self) weakSelf = self; | ||||||||||||||||||||||||||||||||||
| [picker dismissViewControllerAnimated:YES | ||||||||||||||||||||||||||||||||||
| completion:^{ | ||||||||||||||||||||||||||||||||||
| [weakSelf removeInteractionBlocker]; | ||||||||||||||||||||||||||||||||||
| if (weakSelf.callContext == context) { | ||||||||||||||||||||||||||||||||||
| [weakSelf sendCallResultWithPickerInfo:info]; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }]; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
534
to
+545
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The temporary file pointed to by Because this PR defers the execution of To fix this, we should copy the video file immediately within the delegate method before it returns, and then pass the copied destination path to the completion block. We should also use the strong-weak dance ( - (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info {
FLTImagePickerMethodCallContext *context = self.callContext;
__weak typeof(self) weakSelf = self;
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
if (videoURL != nil) {
NSURL *destination = nil;
if (@available(iOS 13.0, *)) {
destination = [FLTImagePickerPhotoAssetUtil saveVideoFromURL:videoURL];
}
[picker dismissViewControllerAnimated:YES
completion:^{
FLTImagePickerPlugin *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf removeInteractionBlocker];
if (strongSelf.callContext == context) {
if (destination == nil) {
[strongSelf sendCallResultWithError:[FlutterError
errorWithCode:"flutter_image_picker_copy_video_error"
message:"Could not cache the video file."
details:nil]];
} else {
[strongSelf sendCallResultWithSavedPathList:@[ destination.path ]];
}
}
}];
} else {
[picker dismissViewControllerAnimated:YES
completion:^{
FLTImagePickerPlugin *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf removeInteractionBlocker];
if (strongSelf.callContext == context) {
[strongSelf sendCallResultWithPickerInfo:info];
}
}];
}
} |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - (void)sendCallResultWithPickerInfo:(NSDictionary<NSString *, id> *)info { | ||||||||||||||||||||||||||||||||||
| // The method dismissViewControllerAnimated does not immediately prevent | ||||||||||||||||||||||||||||||||||
| // further didFinishPickingMediaWithInfo invocations. A nil check is necessary | ||||||||||||||||||||||||||||||||||
| // to prevent below code to be unwantly executed multiple times and cause a | ||||||||||||||||||||||||||||||||||
| // crash. | ||||||||||||||||||||||||||||||||||
| if (!self.callContext) { | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| NSURL *videoURL = info[UIImagePickerControllerMediaURL]; | ||||||||||||||||||||||||||||||||||
| if (videoURL != nil) { | ||||||||||||||||||||||||||||||||||
| if (@available(iOS 13.0, *)) { | ||||||||||||||||||||||||||||||||||
| NSURL *destination = [FLTImagePickerPhotoAssetUtil saveVideoFromURL:videoURL]; | ||||||||||||||||||||||||||||||||||
|
|
@@ -624,12 +631,15 @@ - (void)imagePickerController:(UIImagePickerController *)picker | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { | ||||||||||||||||||||||||||||||||||
| FLTImagePickerMethodCallContext *context = self.callContext; | ||||||||||||||||||||||||||||||||||
| __weak typeof(self) weakSelf = self; | ||||||||||||||||||||||||||||||||||
| [picker dismissViewControllerAnimated:YES | ||||||||||||||||||||||||||||||||||
| completion:^{ | ||||||||||||||||||||||||||||||||||
| [weakSelf removeInteractionBlocker]; | ||||||||||||||||||||||||||||||||||
| if (weakSelf.callContext == context) { | ||||||||||||||||||||||||||||||||||
| [weakSelf sendCallResultWithSavedPathList:nil]; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }]; | ||||||||||||||||||||||||||||||||||
| [self sendCallResultWithSavedPathList:nil]; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
636
to
643
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly to the selection callback, deferring the cancellation result to the dismissal completion block introduces a race condition where a subsequent request's context could be incorrectly cancelled if it is initiated during the dismissal animation. Capture the
Suggested change
Comment on lines
633
to
643
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the strong-weak dance ( - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
FLTImagePickerMethodCallContext *context = self.callContext;
__weak typeof(self) weakSelf = self;
[picker dismissViewControllerAnimated:YES
completion:^{
FLTImagePickerPlugin *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf removeInteractionBlocker];
if (strongSelf.callContext == context) {
[strongSelf sendCallResultWithSavedPathList:nil];
}
}];
} |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| #pragma mark - | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deferring the call to
sendCallResultWithPickerInfo:to the dismissal completion block introduces a race condition. During the dismissal animation (which takes about 300ms), a new platform channel request can be initiated. If a second request is made,cancelInProgressCallwill cancel the first request and overwriteself.callContextwith the new request's context. When the first request's dismissal completion block finally runs, it will deliver the first request's image to the second request's context.To prevent this, capture the
callContextat the moment the picker finishes, and only deliver the result if the captured context matches the current active context.