From b49b02eb6111ee15adec1c8d3c198823c5467724 Mon Sep 17 00:00:00 2001 From: Calvin Ren Date: Sat, 6 Dec 2025 19:57:56 +1000 Subject: [PATCH 1/4] [image_picker] Transparent pressing on iOS 26 (#173453) --- .../image_picker_ios/example/lib/main.dart | 4 +- .../image_picker_ios/FLTImagePickerPlugin.m | 49 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker_ios/example/lib/main.dart b/packages/image_picker/image_picker_ios/example/lib/main.dart index 28fc2875266..cbd2821bfb8 100755 --- a/packages/image_picker/image_picker_ios/example/lib/main.dart +++ b/packages/image_picker/image_picker_ios/example/lib/main.dart @@ -522,7 +522,9 @@ class _MyHomePageState extends State { ? int.parse(limitController.text) : null; onPick(width, height, quality, limit); - Navigator.of(context).pop(); + // Leave the dialog open to verify that tapping the transparent area no longer pops it. + // Regression check for https://github.com/flutter/flutter/issues/173453. + // Navigator.of(context).pop(); }, ), ], diff --git a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m index 8ca93e706a9..01591fec6e4 100644 --- a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m @@ -37,6 +37,8 @@ @interface FLTImagePickerPlugin () /// the array. @property(strong, nonatomic) NSMutableArray *imagePickerControllerOverrides; +@property(strong, nonatomic) UIWindow *interactionBlockerWindow; +@property(weak, nonatomic) UIWindow *previousKeyWindow; @end @@ -323,6 +325,7 @@ - (void)showCamera:(UIImagePickerControllerCameraDevice)device [UIImagePickerController isCameraDeviceAvailable:device]) { imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; imagePickerController.cameraDevice = device; + [self addInteractionBlocker]; [[self viewControllerWithWindow:nil] presentViewController:imagePickerController animated:YES completion:nil]; @@ -532,7 +535,10 @@ - (void)picker:(PHPickerViewController *)picker - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = info[UIImagePickerControllerMediaURL]; - [picker dismissViewControllerAnimated:YES completion:nil]; + [picker dismissViewControllerAnimated:YES + completion:^{ + [self removeInteractionBlocker]; + }]; // 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 @@ -618,7 +624,10 @@ - (void)imagePickerController:(UIImagePickerController *)picker } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [picker dismissViewControllerAnimated:YES completion:nil]; + [picker dismissViewControllerAnimated:YES + completion:^{ + [self removeInteractionBlocker]; + }]; [self sendCallResultWithSavedPathList:nil]; } @@ -674,4 +683,40 @@ - (void)sendCallResultWithError:(FlutterError *)error { self.callContext = nil; } +- (void)addInteractionBlocker { + if (self.interactionBlockerWindow != nil) { + return; + } + UIViewController *topController = [self viewControllerWithWindow:nil]; + UIWindow *presentingWindow = topController.view.window; + if (!presentingWindow) { + return; + } + self.previousKeyWindow = presentingWindow; + UIWindow *blockerWindow; + if (@available(iOS 13.0, *)) { + blockerWindow = [[UIWindow alloc] initWithWindowScene:presentingWindow.windowScene]; + } else { + blockerWindow = [[UIWindow alloc] initWithFrame:presentingWindow.bounds]; + } + blockerWindow.frame = presentingWindow.bounds; + blockerWindow.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + blockerWindow.windowLevel = presentingWindow.windowLevel + 1; + UIViewController *vc = [[UIViewController alloc] init]; + vc.view.backgroundColor = [UIColor clearColor]; + vc.view.userInteractionEnabled = YES; + blockerWindow.rootViewController = vc; + [blockerWindow makeKeyAndVisible]; + self.interactionBlockerWindow = blockerWindow; +} + +- (void)removeInteractionBlocker { + self.interactionBlockerWindow.hidden = YES; + if (self.previousKeyWindow) { + [self.previousKeyWindow makeKeyWindow]; + } + self.interactionBlockerWindow = nil; + self.previousKeyWindow = nil; +} + @end From b15f557de4f920b0f90497b0cd4c081cbf97afca Mon Sep 17 00:00:00 2001 From: Calvin Ren Date: Sat, 6 Dec 2025 19:58:12 +1000 Subject: [PATCH 2/4] update changelog --- packages/image_picker/image_picker_ios/CHANGELOG.md | 4 ++++ packages/image_picker/image_picker_ios/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index a8b8c31a14f..9068b084804 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.13+4 + +* Fixes camera confirmation taps leaking through the picker on some iOS versions (e.g., iOS 26). + ## 0.8.13+3 * Fixes a performance regression on iOS where picking videos could cause a long delay due to transcoding. The picker is now configured to request the original asset to avoid conversion. diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 30b0194fd16..90791363595 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.13+3 +version: 0.8.13+4 environment: sdk: ^3.9.0 From 2f1f2d461abdb879eb43efc36bc753569e34b4ee Mon Sep 17 00:00:00 2001 From: Calvin Ren Date: Sat, 6 Dec 2025 19:58:22 +1000 Subject: [PATCH 3/4] modify from robot review --- .../Sources/image_picker_ios/FLTImagePickerPlugin.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m index 01591fec6e4..dad46642957 100644 --- a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m @@ -694,13 +694,14 @@ - (void)addInteractionBlocker { } self.previousKeyWindow = presentingWindow; UIWindow *blockerWindow; - if (@available(iOS 13.0, *)) { + if (@available(iOS 13.0, *&&presentingWindow.windowScene)) { blockerWindow = [[UIWindow alloc] initWithWindowScene:presentingWindow.windowScene]; } else { blockerWindow = [[UIWindow alloc] initWithFrame:presentingWindow.bounds]; } blockerWindow.frame = presentingWindow.bounds; - blockerWindow.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + blockerWindow.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; blockerWindow.windowLevel = presentingWindow.windowLevel + 1; UIViewController *vc = [[UIViewController alloc] init]; vc.view.backgroundColor = [UIColor clearColor]; @@ -711,6 +712,9 @@ - (void)addInteractionBlocker { } - (void)removeInteractionBlocker { + if (!self.interactionBlockerWindow) { + return; + } self.interactionBlockerWindow.hidden = YES; if (self.previousKeyWindow) { [self.previousKeyWindow makeKeyWindow]; From 628b034a42443e7ba35bdc5c444b3528fdf0c590 Mon Sep 17 00:00:00 2001 From: Calvin Ren Date: Sat, 6 Dec 2025 19:58:31 +1000 Subject: [PATCH 4/4] fix from pr review --- packages/image_picker/image_picker_ios/CHANGELOG.md | 2 +- .../Sources/image_picker_ios/FLTImagePickerPlugin.m | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 9068b084804..95d4a5bb558 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.8.13+4 -* Fixes camera confirmation taps leaking through the picker on some iOS versions (e.g., iOS 26). +* Fixes camera confirmation buttons (e.g., Retake/Use Photo) taps passing through to the underlying Flutter UI while the picker is dismissing on some iOS versions (e.g., iOS 26). ## 0.8.13+3 diff --git a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m index dad46642957..1d49edad3bf 100644 --- a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m @@ -535,9 +535,10 @@ - (void)picker:(PHPickerViewController *)picker - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = info[UIImagePickerControllerMediaURL]; + __weak typeof(self) weakSelf = self; [picker dismissViewControllerAnimated:YES completion:^{ - [self removeInteractionBlocker]; + [weakSelf removeInteractionBlocker]; }]; // The method dismissViewControllerAnimated does not immediately prevent // further didFinishPickingMediaWithInfo invocations. A nil check is necessary @@ -624,9 +625,10 @@ - (void)imagePickerController:(UIImagePickerController *)picker } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { + __weak typeof(self) weakSelf = self; [picker dismissViewControllerAnimated:YES completion:^{ - [self removeInteractionBlocker]; + [weakSelf removeInteractionBlocker]; }]; [self sendCallResultWithSavedPathList:nil]; } @@ -694,7 +696,7 @@ - (void)addInteractionBlocker { } self.previousKeyWindow = presentingWindow; UIWindow *blockerWindow; - if (@available(iOS 13.0, *&&presentingWindow.windowScene)) { + if (@available(iOS 13.0, *) && presentingWindow.windowScene) { blockerWindow = [[UIWindow alloc] initWithWindowScene:presentingWindow.windowScene]; } else { blockerWindow = [[UIWindow alloc] initWithFrame:presentingWindow.bounds];