From 9f587454792c20a19ac0cafdc19c5830d650ec46 Mon Sep 17 00:00:00 2001 From: Chris Rutkowski Date: Wed, 13 May 2020 09:18:57 +0700 Subject: [PATCH 1/7] Makes dynamic lookup for the top view controller in order to present image or camera picker. Existing implementation is incorrect especially when adding project as a module with more complex presentation structure. If window's root view controller has presented modally any other view controller, it cannot present any other any more. Only the top most controller can present other. With this change, the image picker/camera controller is presented on the top most view controller. --- AUTHORS | 3 ++- .../ios/Classes/FLTImagePickerPlugin.m | 26 ++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/AUTHORS b/AUTHORS index b27c156188f8..17ede94e79ba 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,4 +56,5 @@ Giancarlo Rocha Ryo Miyake Théo Champion Kazuki Yamaguchi -Eitan Schwartz \ No newline at end of file +Eitan Schwartz +Chris Rutkowski diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index d01d0928089e..33899d816950 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -25,7 +25,6 @@ @interface FLTImagePickerPlugin () *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/image_picker" binaryMessenger:[registrar messenger]]; - UIViewController *viewController = - [UIApplication sharedApplication].delegate.window.rootViewController; - FLTImagePickerPlugin *instance = - [[FLTImagePickerPlugin alloc] initWithViewController:viewController]; + FLTImagePickerPlugin *instance = [FLTImagePickerPlugin new]; [registrar addMethodCallDelegate:instance channel:channel]; } -- (instancetype)initWithViewController:(UIViewController *)viewController { - self = [super init]; - if (self) { - _viewController = viewController; - } - return self; -} - - (UIImagePickerController *)getImagePickerController { return _imagePickerController; } +- (UIViewController *)viewController { + UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController; + while (topController.presentedViewController) { + topController = topController.presentedViewController; + } + return topController; +} + - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if (self.result) { self.result([FlutterError errorWithCode:@"multiple_request" @@ -136,7 +132,7 @@ - (void)showCamera { [UIImagePickerController isCameraDeviceAvailable:_device]) { _imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; _imagePickerController.cameraDevice = _device; - [_viewController presentViewController:_imagePickerController animated:YES completion:nil]; + [[self viewController] presentViewController:_imagePickerController animated:YES completion:nil]; } else { [[[UIAlertView alloc] initWithTitle:@"Error" message:@"Camera not available." @@ -241,7 +237,7 @@ - (void)errorNoPhotoAccess:(PHAuthorizationStatus)status { - (void)showPhotoLibrary { // No need to check if SourceType is available. It always is. _imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - [_viewController presentViewController:_imagePickerController animated:YES completion:nil]; + [[self viewController] presentViewController:_imagePickerController animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker From a324ff74712cfcd677b5a6bc98f0459950269177 Mon Sep 17 00:00:00 2001 From: Chris Rutkowski Date: Wed, 13 May 2020 10:28:26 +0700 Subject: [PATCH 2/7] style and unit tests --- .../ios/Classes/FLTImagePickerPlugin.m | 8 ++++++-- .../ios/Tests/ImagePickerPluginTests.m | 18 ++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 33899d816950..21192a1e7f6d 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -132,7 +132,9 @@ - (void)showCamera { [UIImagePickerController isCameraDeviceAvailable:_device]) { _imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; _imagePickerController.cameraDevice = _device; - [[self viewController] presentViewController:_imagePickerController animated:YES completion:nil]; + [[self viewController] presentViewController:_imagePickerController + animated:YES + completion:nil]; } else { [[[UIAlertView alloc] initWithTitle:@"Error" message:@"Camera not available." @@ -237,7 +239,9 @@ - (void)errorNoPhotoAccess:(PHAuthorizationStatus)status { - (void)showPhotoLibrary { // No need to check if SourceType is available. It always is. _imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - [[self viewController] presentViewController:_imagePickerController animated:YES completion:nil]; + [[self viewController] presentViewController:_imagePickerController + animated:YES + completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index b7f0d71fb95d..8fbc91bb0066 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -22,8 +22,7 @@ - (void)testPluginPickImageDeviceBack { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; @@ -38,8 +37,7 @@ - (void)testPluginPickImageDeviceFront { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; @@ -54,8 +52,7 @@ - (void)testPluginPickVideoDeviceBack { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; @@ -70,8 +67,7 @@ - (void)testPluginPickVideoDeviceFront { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; @@ -84,8 +80,7 @@ - (void)testPluginPickVideoDeviceFront { #pragma mark - Test video duration - (void)testPickingVideoWithDuration { - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(0), @"maxDuration" : @95}]; @@ -96,8 +91,7 @@ - (void)testPickingVideoWithDuration { } - (void)testPluginPickImageSelectMultipleTimes { - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; From d75a86434929eeed2611547c0bf310cd0590512c Mon Sep 17 00:00:00 2001 From: Chris Rutkowski Date: Wed, 13 May 2020 11:46:04 +0700 Subject: [PATCH 3/7] fixes format issues --- .../image_picker/ios/Classes/FLTImagePickerPlugin.h | 1 - .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h index 38e5b56600f3..925f2597028f 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h @@ -7,7 +7,6 @@ @interface FLTImagePickerPlugin : NSObject // For testing only. -- (instancetype)initWithViewController:(UIViewController *)viewController; - (UIImagePickerController *)getImagePickerController; @end diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 21192a1e7f6d..7dd7b5c71c8d 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -239,9 +239,7 @@ - (void)errorNoPhotoAccess:(PHAuthorizationStatus)status { - (void)showPhotoLibrary { // No need to check if SourceType is available. It always is. _imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - [[self viewController] presentViewController:_imagePickerController - animated:YES - completion:nil]; + [[self viewController] presentViewController:_imagePickerController animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker From 7c0ca0f97714e55abe5959511f17f2f2174403c0 Mon Sep 17 00:00:00 2001 From: Chris Rutkowski Date: Wed, 13 May 2020 13:36:25 +0700 Subject: [PATCH 4/7] bumps version number and updates changelog --- packages/image_picker/image_picker/CHANGELOG.md | 4 ++++ packages/image_picker/image_picker/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 793c5a291a48..149d53b66827 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.7 + +* iOS: Fixes unpresentable album/image picker if window's root view controller is already presenting other view controller. + ## 0.6.6+2 * Update lower bound of dart dependency to 2.1.0. diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 516cdfb120d7..6d69703dc853 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker -version: 0.6.6+2 +version: 0.6.7 flutter: plugin: From 70251671c0b5ae86e27dbbf0ebc953b2b2a69aa3 Mon Sep 17 00:00:00 2001 From: Chris Rutkowski Date: Wed, 20 May 2020 15:18:47 +0700 Subject: [PATCH 5/7] fixes the test after applying merge from different PRs --- .../image_picker/ios/Tests/ImagePickerPluginTests.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index 3383542a1bdc..983d79f9ce37 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -68,8 +68,7 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; From 653b1b1e3831956d5a32c85caf9cdaefb286db8e Mon Sep 17 00:00:00 2001 From: Chris Rutkowski Date: Fri, 12 Jun 2020 12:21:38 +0700 Subject: [PATCH 6/7] exposes method in the interface for testability window in normal circumstances is nil - in that case the key window will be found adds unit tests --- .../ios/Classes/FLTImagePickerPlugin.h | 1 + .../ios/Classes/FLTImagePickerPlugin.m | 24 +++++++++++++----- .../ios/Tests/ImagePickerPluginTests.m | 25 +++++++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h index 925f2597028f..b6d8687a32e3 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h @@ -8,5 +8,6 @@ // For testing only. - (UIImagePickerController *)getImagePickerController; +- (UIViewController *)viewControllerWithWindow:(UIWindow *)window; @end diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index d3b6aba01632..00fdec245aaf 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -40,8 +40,18 @@ - (UIImagePickerController *)getImagePickerController { return _imagePickerController; } -- (UIViewController *)viewController { - UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController; +- (UIViewController *)viewControllerWithWindow:(UIWindow *)window { + UIWindow *windowToUse = window; + if (windowToUse == nil) { + for (UIWindow *window in [UIApplication sharedApplication].windows) { + if (window.isKeyWindow) { + windowToUse = window; + break; + } + } + } + + UIViewController *topController = windowToUse.rootViewController; while (topController.presentedViewController) { topController = topController.presentedViewController; } @@ -132,9 +142,9 @@ - (void)showCamera { [UIImagePickerController isCameraDeviceAvailable:_device]) { _imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; _imagePickerController.cameraDevice = _device; - [[self viewController] presentViewController:_imagePickerController - animated:YES - completion:nil]; + [[self viewControllerWithWindow:nil] presentViewController:_imagePickerController + animated:YES + completion:nil]; } else { [[[UIAlertView alloc] initWithTitle:@"Error" message:@"Camera not available." @@ -239,7 +249,9 @@ - (void)errorNoPhotoAccess:(PHAuthorizationStatus)status { - (void)showPhotoLibrary { // No need to check if SourceType is available. It always is. _imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - [[self viewController] presentViewController:_imagePickerController animated:YES completion:nil]; + [[self viewControllerWithWindow:nil] presentViewController:_imagePickerController + animated:YES + completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index 983d79f9ce37..a516d4cc3a8b 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -7,6 +7,19 @@ @import image_picker; @import XCTest; +@interface MockViewController : UIViewController +@property (nonatomic, retain) UIViewController *mockPresented; +@end + +@implementation MockViewController +@synthesize mockPresented; + +- (UIViewController *)presentedViewController { + return mockPresented; +} + +@end + @interface FLTImagePickerPlugin (Test) @property(copy, nonatomic) FlutterResult result; - (void)handleSavedPath:(NSString *)path; @@ -124,4 +137,16 @@ - (void)testPluginPickImageSelectMultipleTimes { [plugin handleSavedPath:@"test"]; } +- (void)testViewController { + UIWindow *window = [UIWindow new]; + MockViewController *vc1 = [MockViewController new]; + window.rootViewController = vc1; + + UIViewController *vc2 = [UIViewController new]; + vc1.mockPresented = vc2; + + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + XCTAssertEqual([plugin viewControllerWithWindow:window], vc2); +} + @end From e88ed9669ad91340221d6da87f68515343a982b7 Mon Sep 17 00:00:00 2001 From: Chris Rutkowski Date: Fri, 12 Jun 2020 12:32:19 +0700 Subject: [PATCH 7/7] adjust indentation --- .../ios/Tests/ImagePickerPluginTests.m | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index a516d4cc3a8b..c8d5a2bb5368 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -8,14 +8,14 @@ @import XCTest; @interface MockViewController : UIViewController -@property (nonatomic, retain) UIViewController *mockPresented; +@property(nonatomic, retain) UIViewController *mockPresented; @end @implementation MockViewController @synthesize mockPresented; - (UIViewController *)presentedViewController { - return mockPresented; + return mockPresented; } @end @@ -138,15 +138,15 @@ - (void)testPluginPickImageSelectMultipleTimes { } - (void)testViewController { - UIWindow *window = [UIWindow new]; - MockViewController *vc1 = [MockViewController new]; - window.rootViewController = vc1; + UIWindow *window = [UIWindow new]; + MockViewController *vc1 = [MockViewController new]; + window.rootViewController = vc1; - UIViewController *vc2 = [UIViewController new]; - vc1.mockPresented = vc2; + UIViewController *vc2 = [UIViewController new]; + vc1.mockPresented = vc2; - FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; - XCTAssertEqual([plugin viewControllerWithWindow:window], vc2); + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + XCTAssertEqual([plugin viewControllerWithWindow:window], vc2); } @end