diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 158a8811347c..83b953a7ef40 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.8+3 + +* Fixes a possible crash when calling a picker method while another is waiting on permissions. + ## 0.8.8+2 * Adds pub topics to package metadata. diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m index 3ccca17f2e15..4f22c319a72e 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -531,4 +531,83 @@ - (void)testPickImageAuthorizationDenied API_AVAILABLE(ios(14)) { [self waitForExpectationsWithTimeout:30 handler:nil]; } +- (void)testPickMultiImageDuplicateCallCancels API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub([mockPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]) + .andReturn(PHAuthorizationStatusNotDetermined); + OCMExpect([mockPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite + handler:OCMOCK_ANY]); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + XCTestExpectation *firstCallExpectation = [self expectationWithDescription:@"first call"]; + [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@100 height:@100] + quality:nil + fullMetadata:@YES + completion:^(NSArray *result, FlutterError *error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.code, @"multiple_request"); + [firstCallExpectation fulfill]; + }]; + [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@100 height:@100] + quality:nil + fullMetadata:@YES + completion:^(NSArray *result, FlutterError *error){ + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testPickMediaDuplicateCallCancels API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub([mockPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]) + .andReturn(PHAuthorizationStatusNotDetermined); + OCMExpect([mockPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite + handler:OCMOCK_ANY]); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + FLTMediaSelectionOptions *options = + [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)] + imageQuality:@(50) + requestFullMetadata:@YES + allowMultiple:@YES]; + XCTestExpectation *firstCallExpectation = [self expectationWithDescription:@"first call"]; + [plugin pickMediaWithMediaSelectionOptions:options + completion:^(NSArray *result, FlutterError *error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.code, @"multiple_request"); + [firstCallExpectation fulfill]; + }]; + [plugin pickMediaWithMediaSelectionOptions:options + completion:^(NSArray *result, FlutterError *error){ + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + +- (void)testPickVideoDuplicateCallCancels API_AVAILABLE(ios(14)) { + id mockPhotoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub([mockPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]) + .andReturn(PHAuthorizationStatusNotDetermined); + OCMExpect([mockPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite + handler:OCMOCK_ANY]); + + FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; + + FLTSourceSpecification *source = [FLTSourceSpecification makeWithType:FLTSourceTypeCamera + camera:FLTSourceCameraRear]; + XCTestExpectation *firstCallExpectation = [self expectationWithDescription:@"first call"]; + [plugin pickVideoWithSource:source + maxDuration:nil + completion:^(NSString *result, FlutterError *error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.code, @"multiple_request"); + [firstCallExpectation fulfill]; + }]; + [plugin pickVideoWithSource:source + maxDuration:nil + completion:^(NSString *result, FlutterError *error){ + }]; + [self waitForExpectationsWithTimeout:30 handler:nil]; +} + @end diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m index 65120c4254d7..6a4fde82bbbb 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m @@ -31,12 +31,6 @@ - (instancetype)initWithResult:(nonnull FlutterResultAdapter)result { @interface FLTImagePickerPlugin () -/** - * The PHPickerViewController instance used to pick multiple - * images. - */ -@property(strong, nonatomic) PHPickerViewController *pickerViewController API_AVAILABLE(ios(14)); - /** * The UIImagePickerController instances that will be used when a new * controller would normally be created. Each call to @@ -117,15 +111,16 @@ - (void)launchPHPickerWithContext:(nonnull FLTImagePickerMethodCallContext *)con config.filter = [PHPickerFilter imagesFilter]; } - _pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config]; - _pickerViewController.delegate = self; - _pickerViewController.presentationController.delegate = self; + PHPickerViewController *pickerViewController = + [[PHPickerViewController alloc] initWithConfiguration:config]; + pickerViewController.delegate = self; + pickerViewController.presentationController.delegate = self; self.callContext = context; if (context.requestFullMetadata) { - [self checkPhotoAuthorizationForAccessLevel]; + [self checkPhotoAuthorizationWithPHPicker:pickerViewController]; } else { - [self showPhotoLibraryWithPHPicker:_pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; } } @@ -201,6 +196,7 @@ - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize fullMetadata:(NSNumber *)fullMetadata completion:(nonnull void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { + [self cancelInProgressCall]; FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:completion]; context.maxSize = maxSize; @@ -220,6 +216,7 @@ - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize - (void)pickMediaWithMediaSelectionOptions:(nonnull FLTMediaSelectionOptions *)mediaSelectionOptions completion:(nonnull void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { + [self cancelInProgressCall]; FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:completion]; context.maxSize = [mediaSelectionOptions maxSize]; @@ -244,6 +241,7 @@ - (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source maxDuration:(nullable NSNumber *)maxDurationSeconds completion: (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion { + [self cancelInProgressCall]; FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:^void(NSArray *paths, FlutterError *error) { if (paths.count > 1) { @@ -393,7 +391,8 @@ - (void)checkPhotoAuthorizationWithImagePicker:(UIImagePickerController *)imageP } } -- (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { +- (void)checkPhotoAuthorizationWithPHPicker:(PHPickerViewController *)pickerViewController + API_AVAILABLE(ios(14)) { PHAccessLevel requestedAccessLevel = PHAccessLevelReadWrite; PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:requestedAccessLevel]; @@ -404,13 +403,9 @@ - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { handler:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusAuthorized) { - [self - showPhotoLibraryWithPHPicker:self-> - _pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; } else if (status == PHAuthorizationStatusLimited) { - [self - showPhotoLibraryWithPHPicker:self-> - _pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; } else { [self errorNoPhotoAccess:status]; } @@ -420,7 +415,7 @@ - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { } case PHAuthorizationStatusAuthorized: case PHAuthorizationStatusLimited: - [self showPhotoLibraryWithPHPicker:_pickerViewController]; + [self showPhotoLibraryWithPHPicker:pickerViewController]; break; case PHAuthorizationStatusDenied: case PHAuthorizationStatusRestricted: diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 6b71c36d5be2..121dedc41fbe 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.8+2 +version: 0.8.8+3 environment: sdk: ">=2.19.0 <4.0.0"