[image_picker_ios] Pass through error message from image saving (#6858)
* [image_picker_ios] Pass through error message from image saving
* Review edits
* Format
* addObject
diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md
index bff6dd7..f51f46c 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.6+3
+
+* Returns error on image load failure.
+
## 0.8.6+2
* Fixes issue where selectable images of certain types (such as ProRAW images) could not be loaded.
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 320582b..14491b2 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
@@ -6,7 +6,9 @@
@import image_picker_ios;
@import image_picker_ios.Test;
+@import UniformTypeIdentifiers;
@import XCTest;
+
#import <OCMock/OCMock.h>
@interface MockViewController : UIViewController
@@ -269,37 +271,130 @@
- (void)testPluginMultiImagePathHasNullItem {
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
- dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
- __block FlutterError *pickImageResult = nil;
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
- pickImageResult = error;
- dispatch_semaphore_signal(resultSemaphore);
+ XCTAssertEqualObjects(error.code, @"create_error");
+ [resultExpectation fulfill];
}];
[plugin sendCallResultWithSavedPathList:@[ [NSNull null] ]];
- dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);
-
- XCTAssertEqualObjects(pickImageResult.code, @"create_error");
+ [self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)testPluginMultiImagePathHasItem {
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
NSArray *pathList = @[ @"test" ];
- dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
- __block id pickImageResult = nil;
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
- pickImageResult = result;
- dispatch_semaphore_signal(resultSemaphore);
+ XCTAssertEqualObjects(result, pathList);
+ [resultExpectation fulfill];
}];
[plugin sendCallResultWithSavedPathList:pathList];
- dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);
+ [self waitForExpectationsWithTimeout:30 handler:nil];
+}
- XCTAssertEqual(pickImageResult, pathList);
+- (void)testSendsImageInvalidSourceError API_AVAILABLE(ios(14)) {
+ id mockPickerViewController = OCMClassMock([PHPickerViewController class]);
+
+ id mockItemProvider = OCMClassMock([NSItemProvider class]);
+ // Does not conform to image, invalid source.
+ OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(NO);
+
+ PHPickerResult *failResult1 = OCMClassMock([PHPickerResult class]);
+ OCMStub([failResult1 itemProvider]).andReturn(mockItemProvider);
+
+ PHPickerResult *failResult2 = OCMClassMock([PHPickerResult class]);
+ OCMStub([failResult2 itemProvider]).andReturn(mockItemProvider);
+
+ FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
+ initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
+ XCTAssertTrue(NSThread.isMainThread);
+ XCTAssertNil(result);
+ XCTAssertEqualObjects(error.code, @"invalid_source");
+ [resultExpectation fulfill];
+ }];
+
+ [plugin picker:mockPickerViewController didFinishPicking:@[ failResult1, failResult2 ]];
+
+ [self waitForExpectationsWithTimeout:30 handler:nil];
+}
+
+- (void)testSendsImageInvalidErrorWhenOneFails API_AVAILABLE(ios(14)) {
+ id mockPickerViewController = OCMClassMock([PHPickerViewController class]);
+ NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil];
+
+ id mockFailItemProvider = OCMClassMock([NSItemProvider class]);
+ OCMStub([mockFailItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES);
+ [[mockFailItemProvider stub]
+ loadDataRepresentationForTypeIdentifier:OCMOCK_ANY
+ completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null],
+ loadDataError, nil]];
+
+ PHPickerResult *failResult = OCMClassMock([PHPickerResult class]);
+ OCMStub([failResult itemProvider]).andReturn(mockFailItemProvider);
+
+ NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage"
+ withExtension:@"tiff"];
+ NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL];
+ PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]);
+ OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider);
+
+ FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
+ initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
+ XCTAssertTrue(NSThread.isMainThread);
+ XCTAssertNil(result);
+ XCTAssertEqualObjects(error.code, @"invalid_image");
+ [resultExpectation fulfill];
+ }];
+
+ [plugin picker:mockPickerViewController didFinishPicking:@[ failResult, tiffResult ]];
+
+ [self waitForExpectationsWithTimeout:30 handler:nil];
+}
+
+- (void)testSavesImages API_AVAILABLE(ios(14)) {
+ id mockPickerViewController = OCMClassMock([PHPickerViewController class]);
+
+ NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage"
+ withExtension:@"tiff"];
+ NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL];
+ PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]);
+ OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider);
+
+ NSURL *pngURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage"
+ withExtension:@"png"];
+ NSItemProvider *pngItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:pngURL];
+ PHPickerResult *pngResult = OCMClassMock([PHPickerResult class]);
+ OCMStub([pngResult itemProvider]).andReturn(pngItemProvider);
+
+ FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+
+ XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
+
+ plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
+ initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
+ XCTAssertTrue(NSThread.isMainThread);
+ XCTAssertEqual(result.count, 2);
+ XCTAssertNil(error);
+ [resultExpectation fulfill];
+ }];
+
+ [plugin picker:mockPickerViewController didFinishPicking:@[ tiffResult, pngResult ]];
+
+ [self waitForExpectationsWithTimeout:30 handler:nil];
}
@end
diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m
index d211ea3..41398bf 100644
--- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m
+++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m
@@ -39,8 +39,7 @@
maxWidth:nil
maxHeight:nil
imageQuality:nil];
- XCTAssertNotNil(savedPathJPG);
- XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4], @".jpg");
+ XCTAssertEqualObjects([NSURL URLWithString:savedPathJPG].pathExtension, @"jpg");
NSDictionary *originalMetaDataJPG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataJPG];
NSData *newDataJPG = [NSData dataWithContentsOfFile:savedPathJPG];
@@ -55,8 +54,7 @@
maxWidth:nil
maxHeight:nil
imageQuality:nil];
- XCTAssertNotNil(savedPathPNG);
- XCTAssertEqualObjects([savedPathPNG substringFromIndex:savedPathPNG.length - 4], @".png");
+ XCTAssertEqualObjects([NSURL URLWithString:savedPathPNG].pathExtension, @"png");
NSDictionary *originalMetaDataPNG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataPNG];
NSData *newDataPNG = [NSData dataWithContentsOfFile:savedPathPNG];
@@ -69,8 +67,6 @@
NSString *savedPathJPG = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil
image:imageJPG
imageQuality:nil];
-
- XCTAssertNotNil(savedPathJPG);
// should be saved as
XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4],
kFLTImagePickerDefaultSuffix);
@@ -98,7 +94,7 @@
// test gif
NSData *dataGIF = ImagePickerTestImages.GIFTestData;
UIImage *imageGIF = [UIImage imageWithData:dataGIF];
- CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil);
+ CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil);
size_t numberOfFrames = CGImageSourceGetCount(imageSource);
@@ -107,12 +103,12 @@
maxWidth:nil
maxHeight:nil
imageQuality:nil];
- XCTAssertNotNil(savedPathGIF);
- XCTAssertEqualObjects([savedPathGIF substringFromIndex:savedPathGIF.length - 4], @".gif");
+ XCTAssertEqualObjects([NSURL URLWithString:savedPathGIF].pathExtension, @"gif");
NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPathGIF];
- CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil);
+ CGImageSourceRef newImageSource =
+ CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil);
size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource);
@@ -124,7 +120,7 @@
NSData *dataGIF = ImagePickerTestImages.GIFTestData;
UIImage *imageGIF = [UIImage imageWithData:dataGIF];
- CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil);
+ CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil);
size_t numberOfFrames = CGImageSourceGetCount(imageSource);
@@ -139,7 +135,8 @@
XCTAssertEqual(newImage.size.width, 3);
XCTAssertEqual(newImage.size.height, 2);
- CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil);
+ CGImageSourceRef newImageSource =
+ CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil);
size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource);
diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m
index 5594b9d..d418354 100644
--- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m
+++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m
@@ -3,10 +3,10 @@
// found in the LICENSE file.
#import <OCMock/OCMock.h>
-#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
@import image_picker_ios;
@import image_picker_ios.Test;
+@import UniformTypeIdentifiers;
@import XCTest;
@interface PickerSaveImageToPathOperationTests : XCTestCase
@@ -113,6 +113,60 @@
[self verifySavingImageWithPickerResult:result fullMetadata:YES];
}
+- (void)testNonexistentImage API_AVAILABLE(ios(14)) {
+ NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"bogus"
+ withExtension:@"png"];
+ NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
+ PHPickerResult *result = [self createPickerResultWithProvider:itemProvider];
+
+ XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid source error"];
+ FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
+ initWithResult:result
+ maxHeight:@100
+ maxWidth:@100
+ desiredImageQuality:@100
+ fullMetadata:YES
+ savedPathBlock:^(NSString *savedPath, FlutterError *error) {
+ XCTAssertEqualObjects(error.code, @"invalid_source");
+ [errorExpectation fulfill];
+ }];
+
+ [operation start];
+ [self waitForExpectationsWithTimeout:30 handler:nil];
+}
+
+- (void)testFailingImageLoad API_AVAILABLE(ios(14)) {
+ NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil];
+
+ id mockItemProvider = OCMClassMock([NSItemProvider class]);
+ OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES);
+ [[mockItemProvider stub]
+ loadDataRepresentationForTypeIdentifier:OCMOCK_ANY
+ completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null],
+ loadDataError, nil]];
+
+ id pickerResult = OCMClassMock([PHPickerResult class]);
+ OCMStub([pickerResult itemProvider]).andReturn(mockItemProvider);
+
+ XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid image error"];
+
+ FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
+ initWithResult:pickerResult
+ maxHeight:@100
+ maxWidth:@100
+ desiredImageQuality:@100
+ fullMetadata:YES
+ savedPathBlock:^(NSString *savedPath, FlutterError *error) {
+ XCTAssertEqualObjects(error.code, @"invalid_image");
+ XCTAssertEqualObjects(error.message, loadDataError.localizedDescription);
+ XCTAssertEqualObjects(error.details, @"PHPickerDomain");
+ [errorExpectation fulfill];
+ }];
+
+ [operation start];
+ [self waitForExpectationsWithTimeout:30 handler:nil];
+}
+
- (void)testSavePNGImageWithoutFullMetadata API_AVAILABLE(ios(14)) {
id photoAssetUtil = OCMClassMock([PHAsset class]);
@@ -120,10 +174,10 @@
withExtension:@"png"];
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
PHPickerResult *result = [self createPickerResultWithProvider:itemProvider];
+ OCMReject([photoAssetUtil fetchAssetsWithLocalIdentifiers:OCMOCK_ANY options:OCMOCK_ANY]);
[self verifySavingImageWithPickerResult:result fullMetadata:NO];
- OCMVerify(times(0), [photoAssetUtil fetchAssetsWithLocalIdentifiers:[OCMArg any]
- options:[OCMArg any]]);
+ OCMVerifyAll(photoAssetUtil);
}
/**
@@ -153,6 +207,8 @@
- (void)verifySavingImageWithPickerResult:(PHPickerResult *)result
fullMetadata:(BOOL)fullMetadata API_AVAILABLE(ios(14)) {
XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"];
+ XCTestExpectation *operationExpectation =
+ [self expectationWithDescription:@"Operation completed"];
FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
initWithResult:result
@@ -160,14 +216,17 @@
maxWidth:@100
desiredImageQuality:@100
fullMetadata:fullMetadata
- savedPathBlock:^(NSString *savedPath) {
- if ([[NSFileManager defaultManager] fileExistsAtPath:savedPath]) {
- [pathExpectation fulfill];
- }
+ savedPathBlock:^(NSString *savedPath, FlutterError *error) {
+ XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]);
+ [pathExpectation fulfill];
}];
+ operation.completionBlock = ^{
+ [operationExpectation fulfill];
+ };
[operation start];
- [self waitForExpectations:@[ pathExpectation ] timeout:30];
+ [self waitForExpectationsWithTimeout:30 handler:nil];
+ XCTAssertTrue(operation.isFinished);
}
@end
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m
index 2d370aa..d5b823c 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m
@@ -120,7 +120,7 @@
options[(NSString *)kCGImageSourceTypeIdentifierHint] = (NSString *)kUTTypeGIF;
CGImageSourceRef imageSource =
- CGImageSourceCreateWithData((CFDataRef)data, (CFDictionaryRef)options);
+ CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options);
size_t numberOfFrames = CGImageSourceGetCount(imageSource);
NSMutableArray<UIImage *> *images = [NSMutableArray arrayWithCapacity:numberOfFrames];
@@ -128,7 +128,7 @@
NSTimeInterval interval = 0.0;
for (size_t index = 0; index < numberOfFrames; index++) {
CGImageRef imageRef =
- CGImageSourceCreateImageAtIndex(imageSource, index, (CFDictionaryRef)options);
+ CGImageSourceCreateImageAtIndex(imageSource, index, (__bridge CFDictionaryRef)options);
NSDictionary *properties = (NSDictionary *)CFBridgingRelease(
CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL));
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.m
index 45bcaa7..1954625 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.m
@@ -42,7 +42,7 @@
}
+ (NSDictionary *)getMetaDataFromImageData:(NSData *)imageData {
- CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
+ CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
NSDictionary *metadata =
(NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL));
CFRelease(source);
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m
index 37a1a98..fef94ad 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m
@@ -103,7 +103,7 @@
gifInfo:(GIFInfo *)gifInfo
path:(NSString *)path {
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(
- (CFURLRef)[NSURL fileURLWithPath:path], kUTTypeGIF, gifInfo.images.count, NULL);
+ (__bridge CFURLRef)[NSURL fileURLWithPath:path], kUTTypeGIF, gifInfo.images.count, NULL);
NSDictionary *frameProperties = @{
(__bridge NSString *)kCGImagePropertyGIFDictionary : @{
@@ -120,11 +120,12 @@
gifProperties[(__bridge NSString *)kCGImagePropertyGIFLoopCount] = @0;
- CGImageDestinationSetProperties(destination, (CFDictionaryRef)gifMetaProperties);
+ CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)gifMetaProperties);
for (NSInteger index = 0; index < gifInfo.images.count; index++) {
UIImage *image = (UIImage *)[gifInfo.images objectAtIndex:index];
- CGImageDestinationAddImage(destination, image.CGImage, (CFDictionaryRef)frameProperties);
+ CGImageDestinationAddImage(destination, image.CGImage,
+ (__bridge CFDictionaryRef)frameProperties);
}
CGImageDestinationFinalize(destination);
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.h
index c88db0b..626e2ba 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.h
@@ -5,9 +5,9 @@
#import <Flutter/Flutter.h>
#import <PhotosUI/PhotosUI.h>
+NS_ASSUME_NONNULL_BEGIN
+
@interface FLTImagePickerPlugin : NSObject <FlutterPlugin>
-
-// For testing only.
-- (UIViewController *)viewControllerWithWindow:(UIWindow *)window;
-
@end
+
+NS_ASSUME_NONNULL_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 27b06ba..68230ed 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
@@ -29,10 +29,7 @@
#pragma mark -
-@interface FLTImagePickerPlugin () <UINavigationControllerDelegate,
- UIImagePickerControllerDelegate,
- PHPickerViewControllerDelegate,
- UIAdaptivePresentationControllerDelegate>
+@interface FLTImagePickerPlugin ()
/**
* The PHPickerViewController instance used to pick multiple
@@ -478,52 +475,55 @@
[self sendCallResultWithSavedPathList:nil];
return;
}
- dispatch_queue_t backgroundQueue =
- dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
- dispatch_async(backgroundQueue, ^{
- NSNumber *maxWidth = self.callContext.maxSize.width;
- NSNumber *maxHeight = self.callContext.maxSize.height;
- NSNumber *imageQuality = self.callContext.imageQuality;
- NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality];
- NSOperationQueue *operationQueue = [NSOperationQueue new];
- NSMutableArray *pathList = [self createNSMutableArrayWithSize:results.count];
+ __block NSOperationQueue *saveQueue = [[NSOperationQueue alloc] init];
+ saveQueue.name = @"Flutter Save Image Queue";
+ saveQueue.qualityOfService = NSQualityOfServiceUserInitiated;
- for (int i = 0; i < results.count; i++) {
- PHPickerResult *result = results[i];
- FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
- initWithResult:result
- maxHeight:maxHeight
- maxWidth:maxWidth
- desiredImageQuality:desiredImageQuality
- fullMetadata:self.callContext.requestFullMetadata
- savedPathBlock:^(NSString *savedPath) {
- pathList[i] = savedPath;
- }];
- [operationQueue addOperation:operation];
+ FLTImagePickerMethodCallContext *currentCallContext = self.callContext;
+ NSNumber *maxWidth = currentCallContext.maxSize.width;
+ NSNumber *maxHeight = currentCallContext.maxSize.height;
+ NSNumber *imageQuality = currentCallContext.imageQuality;
+ NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality];
+ BOOL requestFullMetadata = currentCallContext.requestFullMetadata;
+ NSMutableArray *pathList = [[NSMutableArray alloc] initWithCapacity:results.count];
+ __block FlutterError *saveError = nil;
+ __weak typeof(self) weakSelf = self;
+ // This operation will be executed on the main queue after
+ // all selected files have been saved.
+ NSBlockOperation *sendListOperation = [NSBlockOperation blockOperationWithBlock:^{
+ if (saveError != nil) {
+ [weakSelf sendCallResultWithError:saveError];
+ } else {
+ [weakSelf sendCallResultWithSavedPathList:pathList];
}
- [operationQueue waitUntilAllOperationsAreFinished];
- dispatch_async(dispatch_get_main_queue(), ^{
- [self sendCallResultWithSavedPathList:pathList];
- });
- });
-}
+ // Retain queue until here.
+ saveQueue = nil;
+ }];
-#pragma mark -
+ [results enumerateObjectsUsingBlock:^(PHPickerResult *result, NSUInteger index, BOOL *stop) {
+ // NSNull means it hasn't saved yet.
+ [pathList addObject:[NSNull null]];
+ FLTPHPickerSaveImageToPathOperation *saveOperation =
+ [[FLTPHPickerSaveImageToPathOperation alloc]
+ initWithResult:result
+ maxHeight:maxHeight
+ maxWidth:maxWidth
+ desiredImageQuality:desiredImageQuality
+ fullMetadata:requestFullMetadata
+ savedPathBlock:^(NSString *savedPath, FlutterError *error) {
+ if (savedPath != nil) {
+ pathList[index] = savedPath;
+ } else {
+ saveError = error;
+ }
+ }];
+ [sendListOperation addDependency:saveOperation];
+ [saveQueue addOperation:saveOperation];
+ }];
-/**
- * Creates an NSMutableArray of a certain size filled with NSNull objects.
- *
- * The difference with initWithCapacity is that initWithCapacity still gives an empty array making
- * it impossible to add objects on an index larger than the size.
- *
- * @param size The length of the required array
- * @return NSMutableArray An array of a specified size
- */
-- (NSMutableArray *)createNSMutableArrayWithSize:(NSUInteger)size {
- NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:size];
- for (int i = 0; i < size; [mutableArray addObject:[NSNull null]], i++)
- ;
- return mutableArray;
+ // Schedule the final Flutter callback on the main queue
+ // to be run after all images have been saved.
+ [NSOperationQueue.mainQueue addOperation:sendListOperation];
}
#pragma mark - UIImagePickerControllerDelegate
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
index d73a54d..f849211 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
@@ -54,13 +54,19 @@
#pragma mark -
/** Methods exposed for unit testing. */
-@interface FLTImagePickerPlugin () <FLTImagePickerApi>
+@interface FLTImagePickerPlugin () <FLTImagePickerApi,
+ UINavigationControllerDelegate,
+ UIImagePickerControllerDelegate,
+ PHPickerViewControllerDelegate,
+ UIAdaptivePresentationControllerDelegate>
/**
* The context of the Flutter method call that is currently being handled, if any.
*/
@property(strong, nonatomic, nullable) FLTImagePickerMethodCallContext *callContext;
+- (UIViewController *)viewControllerWithWindow:(nullable UIWindow *)window;
+
/**
* Validates the provided paths list, then sends it via `callContext.result` as the result of the
* original platform channel method call, clearing the in-progress call state.
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h
index 8e097072..00c1f1d 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h
@@ -9,6 +9,11 @@
#import "FLTImagePickerMetaDataUtil.h"
#import "FLTImagePickerPhotoAssetUtil.h"
+NS_ASSUME_NONNULL_BEGIN
+
+/// Returns either the saved path, or an error. Both cannot be set.
+typedef void (^FLTGetSavedPath)(NSString *_Nullable savedPath, FlutterError *_Nullable error);
+
/*!
@class FLTPHPickerSaveImageToPathOperation
@@ -27,6 +32,8 @@
maxWidth:(NSNumber *)maxWidth
desiredImageQuality:(NSNumber *)desiredImageQuality
fullMetadata:(BOOL)fullMetadata
- savedPathBlock:(void (^)(NSString *))savedPathBlock API_AVAILABLE(ios(14));
+ savedPathBlock:(FLTGetSavedPath)savedPathBlock API_AVAILABLE(ios(14));
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
index 16c2050..9a4ae2f 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#import <Flutter/Flutter.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "FLTPHPickerSaveImageToPathOperation.h"
@@ -19,12 +20,10 @@
@end
-typedef void (^GetSavedPath)(NSString *);
-
@implementation FLTPHPickerSaveImageToPathOperation {
BOOL executing;
BOOL finished;
- GetSavedPath getSavedPath;
+ FLTGetSavedPath getSavedPath;
}
- (instancetype)initWithResult:(PHPickerResult *)result
@@ -32,7 +31,7 @@
maxWidth:(NSNumber *)maxWidth
desiredImageQuality:(NSNumber *)desiredImageQuality
fullMetadata:(BOOL)fullMetadata
- savedPathBlock:(GetSavedPath)savedPathBlock API_AVAILABLE(ios(14)) {
+ savedPathBlock:(FLTGetSavedPath)savedPathBlock API_AVAILABLE(ios(14)) {
if (self = [super init]) {
if (result) {
self.result = result;
@@ -76,10 +75,10 @@
[self didChangeValueForKey:@"isExecuting"];
}
-- (void)completeOperationWithPath:(NSString *)savedPath {
+- (void)completeOperationWithPath:(NSString *)savedPath error:(FlutterError *)error {
+ getSavedPath(savedPath, error);
[self setExecuting:NO];
[self setFinished:YES];
- getSavedPath(savedPath);
}
- (void)start {
@@ -102,10 +101,18 @@
UIImage *image = [[UIImage alloc] initWithData:data];
[self processImage:image];
} else {
- os_log_error(OS_LOG_DEFAULT, "Could not process image: %@",
- error);
+ FlutterError *flutterError =
+ [FlutterError errorWithCode:@"invalid_image"
+ message:error.localizedDescription
+ details:error.domain];
+ [self completeOperationWithPath:nil error:flutterError];
}
}];
+ } else {
+ FlutterError *flutterError = [FlutterError errorWithCode:@"invalid_source"
+ message:@"Invalid image source."
+ details:nil];
+ [self completeOperationWithPath:nil error:flutterError];
}
} else {
[self setFinished:YES];
@@ -139,7 +146,7 @@
maxWidth:self.maxWidth
maxHeight:self.maxHeight
imageQuality:self.desiredImageQuality];
- [self completeOperationWithPath:savedPath];
+ [self completeOperationWithPath:savedPath error:nil];
};
if (@available(iOS 13.0, *)) {
[[PHImageManager defaultManager]
@@ -169,7 +176,7 @@
[FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil
image:localImage
imageQuality:self.desiredImageQuality];
- [self completeOperationWithPath:savedPath];
+ [self completeOperationWithPath:savedPath error:nil];
}
}
diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml
index e1b3891..44c00d7 100755
--- a/packages/image_picker/image_picker_ios/pubspec.yaml
+++ b/packages/image_picker/image_picker_ios/pubspec.yaml
@@ -2,7 +2,7 @@
description: iOS implementation of the image_picker plugin.
repository: https://github.com/flutter/plugins/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.6+2
+version: 0.8.6+3
environment:
sdk: ">=2.14.0 <3.0.0"