Skip to content

Commit 90411ac

Browse files
author
Calvin Ren
committed
[image_picker] Transparent pressing on iOS 26 (#173453)
Issue: On iOS, taps on the camera confirmation bar (“Use Photo” / “Retake”) were leaking through UIImagePickerController and dismissing underlying UI (e.g., bottom sheets) — a system touch-through regression reported on iOS 26 and some 18.x devices. What I changed: In ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m, before presenting the camera picker I now create a transparent, high-level UIWindow to absorb touches and restore the previous key window once the picker finishes or is cancelled. This aims to block those leaked taps without changing Flutter-side UX.
1 parent c8be05d commit 90411ac

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

packages/image_picker/image_picker_ios/example/lib/main.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,9 @@ class _MyHomePageState extends State<MyHomePage> {
522522
? int.parse(limitController.text)
523523
: null;
524524
onPick(width, height, quality, limit);
525-
Navigator.of(context).pop();
525+
// Leave the dialog open to verify that tapping the transparent area no longer pops it.
526+
// Regression check for https://github.com/flutter/flutter/issues/173453.
527+
// Navigator.of(context).pop();
526528
},
527529
),
528530
],

packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ @interface FLTImagePickerPlugin ()
3737
/// the array.
3838
@property(strong, nonatomic)
3939
NSMutableArray<UIImagePickerController *> *imagePickerControllerOverrides;
40+
@property(strong, nonatomic) UIWindow *interactionBlockerWindow;
41+
@property(weak, nonatomic) UIWindow *previousKeyWindow;
4042

4143
@end
4244

@@ -322,6 +324,7 @@ - (void)showCamera:(UIImagePickerControllerCameraDevice)device
322324
[UIImagePickerController isCameraDeviceAvailable:device]) {
323325
imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
324326
imagePickerController.cameraDevice = device;
327+
[self addInteractionBlocker];
325328
[[self viewControllerWithWindow:nil] presentViewController:imagePickerController
326329
animated:YES
327330
completion:nil];
@@ -531,7 +534,10 @@ - (void)picker:(PHPickerViewController *)picker
531534
- (void)imagePickerController:(UIImagePickerController *)picker
532535
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info {
533536
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
534-
[picker dismissViewControllerAnimated:YES completion:nil];
537+
[picker dismissViewControllerAnimated:YES
538+
completion:^{
539+
[self removeInteractionBlocker];
540+
}];
535541
// The method dismissViewControllerAnimated does not immediately prevent
536542
// further didFinishPickingMediaWithInfo invocations. A nil check is necessary
537543
// to prevent below code to be unwantly executed multiple times and cause a
@@ -617,7 +623,10 @@ - (void)imagePickerController:(UIImagePickerController *)picker
617623
}
618624

619625
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
620-
[picker dismissViewControllerAnimated:YES completion:nil];
626+
[picker dismissViewControllerAnimated:YES
627+
completion:^{
628+
[self removeInteractionBlocker];
629+
}];
621630
[self sendCallResultWithSavedPathList:nil];
622631
}
623632

@@ -673,4 +682,40 @@ - (void)sendCallResultWithError:(FlutterError *)error {
673682
self.callContext = nil;
674683
}
675684

685+
- (void)addInteractionBlocker {
686+
if (self.interactionBlockerWindow != nil) {
687+
return;
688+
}
689+
UIViewController *topController = [self viewControllerWithWindow:nil];
690+
UIWindow *presentingWindow = topController.view.window;
691+
if (!presentingWindow) {
692+
return;
693+
}
694+
self.previousKeyWindow = presentingWindow;
695+
UIWindow *blockerWindow;
696+
if (@available(iOS 13.0, *)) {
697+
blockerWindow = [[UIWindow alloc] initWithWindowScene:presentingWindow.windowScene];
698+
} else {
699+
blockerWindow = [[UIWindow alloc] initWithFrame:presentingWindow.bounds];
700+
}
701+
blockerWindow.frame = presentingWindow.bounds;
702+
blockerWindow.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
703+
blockerWindow.windowLevel = presentingWindow.windowLevel + 1;
704+
UIViewController *vc = [[UIViewController alloc] init];
705+
vc.view.backgroundColor = [UIColor clearColor];
706+
vc.view.userInteractionEnabled = YES;
707+
blockerWindow.rootViewController = vc;
708+
[blockerWindow makeKeyAndVisible];
709+
self.interactionBlockerWindow = blockerWindow;
710+
}
711+
712+
- (void)removeInteractionBlocker {
713+
self.interactionBlockerWindow.hidden = YES;
714+
if (self.previousKeyWindow) {
715+
[self.previousKeyWindow makeKeyWindow];
716+
}
717+
self.interactionBlockerWindow = nil;
718+
self.previousKeyWindow = nil;
719+
}
720+
676721
@end

0 commit comments

Comments
 (0)