[image_picker] add requestFullMetadata for iOS (optional permissions) - iOS changes (#5713)

diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md
index c145013..33e2b61 100644
--- a/packages/image_picker/image_picker_ios/CHANGELOG.md
+++ b/packages/image_picker/image_picker_ios/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 0.8.6
 
+* Adds `requestFullMetadata` option to `pickImage`, so images on iOS can be picked without `Photo Library Usage` permission.
 * Updates minimum Flutter version to 2.10.
 
 ## 0.8.5+6
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 04d4911..320582b 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
@@ -46,7 +46,7 @@
       .andReturn(AVAuthorizationStatusAuthorized);
 
   // Run test
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   UIImagePickerController *controller = [[UIImagePickerController alloc] init];
   [plugin setImagePickerControllerOverrides:@[ controller ]];
 
@@ -54,6 +54,7 @@
                                                             camera:FLTSourceCameraRear]
                       maxSize:[[FLTMaxSize alloc] init]
                       quality:nil
+                 fullMetadata:@(YES)
                    completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
                    }];
 
@@ -78,7 +79,7 @@
       .andReturn(AVAuthorizationStatusAuthorized);
 
   // Run test
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   UIImagePickerController *controller = [[UIImagePickerController alloc] init];
   [plugin setImagePickerControllerOverrides:@[ controller ]];
 
@@ -86,6 +87,7 @@
                                                             camera:FLTSourceCameraFront]
                       maxSize:[[FLTMaxSize alloc] init]
                       quality:nil
+                 fullMetadata:@(YES)
                    completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
                    }];
 
@@ -110,7 +112,7 @@
       .andReturn(AVAuthorizationStatusAuthorized);
 
   // Run test
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   UIImagePickerController *controller = [[UIImagePickerController alloc] init];
   [plugin setImagePickerControllerOverrides:@[ controller ]];
 
@@ -142,7 +144,7 @@
       .andReturn(AVAuthorizationStatusAuthorized);
 
   // Run test
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   UIImagePickerController *controller = [[UIImagePickerController alloc] init];
   [plugin setImagePickerControllerOverrides:@[ controller ]];
 
@@ -165,11 +167,12 @@
   OCMStub(ClassMethod([photoLibrary authorizationStatus]))
       .andReturn(PHAuthorizationStatusAuthorized);
 
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
 
   [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
                             quality:@(50)
+                       fullMetadata:@(YES)
                          completion:^(NSArray<NSString *> *_Nullable result,
                                       FlutterError *_Nullable error){
                          }];
@@ -177,13 +180,48 @@
             [mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]);
 }
 
+- (void)testPickImageWithoutFullMetadata API_AVAILABLE(ios(11)) {
+  id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
+  id photoLibrary = OCMClassMock([PHPhotoLibrary class]);
+
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
+
+  [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery
+                                                            camera:FLTSourceCameraFront]
+                      maxSize:[[FLTMaxSize alloc] init]
+                      quality:nil
+                 fullMetadata:@(NO)
+                   completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
+                   }];
+
+  OCMVerify(times(0), [photoLibrary authorizationStatus]);
+}
+
+- (void)testPickMultiImageWithoutFullMetadata API_AVAILABLE(ios(11)) {
+  id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
+  id photoLibrary = OCMClassMock([PHPhotoLibrary class]);
+
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
+
+  [plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init]
+                            quality:nil
+                       fullMetadata:@(NO)
+                         completion:^(NSArray<NSString *> *_Nullable result,
+                                      FlutterError *_Nullable error){
+                         }];
+
+  OCMVerify(times(0), [photoLibrary authorizationStatus]);
+}
+
 #pragma mark - Test camera devices, no op on simulators
 
 - (void)testPluginPickImageDeviceCancelClickMultipleTimes {
   if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
     return;
   }
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   UIImagePickerController *controller = [[UIImagePickerController alloc] init];
   plugin.imagePickerControllerOverrides = @[ controller ];
 
@@ -191,6 +229,7 @@
                                                             camera:FLTSourceCameraRear]
                       maxSize:[[FLTMaxSize alloc] init]
                       quality:nil
+                 fullMetadata:@(YES)
                    completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
                    }];
 
@@ -202,7 +241,7 @@
 #pragma mark - Test video duration
 
 - (void)testPickingVideoWithDuration {
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   UIImagePickerController *controller = [[UIImagePickerController alloc] init];
   [plugin setImagePickerControllerOverrides:@[ controller ]];
 
@@ -223,12 +262,12 @@
   UIViewController *vc2 = [UIViewController new];
   vc1.mockPresented = vc2;
 
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   XCTAssertEqual([plugin viewControllerWithWindow:window], vc2);
 }
 
 - (void)testPluginMultiImagePathHasNullItem {
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
 
   dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
   __block FlutterError *pickImageResult = nil;
@@ -245,7 +284,7 @@
 }
 
 - (void)testPluginMultiImagePathHasItem {
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
   NSArray *pathList = @[ @"test" ];
 
   dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
diff --git a/packages/image_picker/image_picker_ios/example/pubspec.yaml b/packages/image_picker/image_picker_ios/example/pubspec.yaml
index 24b3af0..bca58a5 100755
--- a/packages/image_picker/image_picker_ios/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_ios/example/pubspec.yaml
@@ -16,7 +16,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  image_picker_platform_interface: ^2.3.0
+  image_picker_platform_interface: ^2.6.1
   video_player: ^2.1.4
 
 dev_dependencies:
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 18d4ad2..fa1bb66 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
@@ -56,7 +56,7 @@
 @implementation FLTImagePickerPlugin
 
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
-  FLTImagePickerPlugin *instance = [FLTImagePickerPlugin new];
+  FLTImagePickerPlugin *instance = [[FLTImagePickerPlugin alloc] init];
   FLTImagePickerApiSetup(registrar.messenger, instance);
 }
 
@@ -119,7 +119,11 @@
   _pickerViewController.presentationController.delegate = self;
   self.callContext = context;
 
-  [self checkPhotoAuthorizationForAccessLevel];
+  if (context.requestFullMetadata) {
+    [self checkPhotoAuthorizationForAccessLevel];
+  } else {
+    [self showPhotoLibraryWithPHPicker:_pickerViewController];
+  }
 }
 
 - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source
@@ -136,7 +140,16 @@
                                              camera:[self cameraDeviceForSource:source]];
       break;
     case FLTSourceTypeGallery:
-      [self checkPhotoAuthorizationWithImagePicker:imagePickerController];
+      if (@available(iOS 11, *)) {
+        if (context.requestFullMetadata) {
+          [self checkPhotoAuthorizationWithImagePicker:imagePickerController];
+        } else {
+          [self showPhotoLibraryWithImagePicker:imagePickerController];
+        }
+      } else {
+        // Prior to iOS 11, accessing gallery requires authorization
+        [self checkPhotoAuthorizationWithImagePicker:imagePickerController];
+      }
       break;
     default:
       [self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source"
@@ -151,6 +164,7 @@
 - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source
                     maxSize:(nonnull FLTMaxSize *)maxSize
                     quality:(nullable NSNumber *)imageQuality
+               fullMetadata:(NSNumber *)fullMetadata
                  completion:
                      (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion {
   [self cancelInProgressCall];
@@ -166,6 +180,7 @@
   context.maxSize = maxSize;
   context.imageQuality = imageQuality;
   context.maxImageCount = 1;
+  context.requestFullMetadata = [fullMetadata boolValue];
 
   if (source.type == FLTSourceTypeGallery) {  // Capture is not possible with PHPicker
     if (@available(iOS 14, *)) {
@@ -180,12 +195,14 @@
 
 - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize
                           quality:(nullable NSNumber *)imageQuality
+                     fullMetadata:(NSNumber *)fullMetadata
                        completion:(nonnull void (^)(NSArray<NSString *> *_Nullable,
                                                     FlutterError *_Nullable))completion {
   FLTImagePickerMethodCallContext *context =
       [[FLTImagePickerMethodCallContext alloc] initWithResult:completion];
   context.maxSize = maxSize;
   context.imageQuality = imageQuality;
+  context.requestFullMetadata = [fullMetadata boolValue];
 
   if (@available(iOS 14, *)) {
     [self launchPHPickerWithContext:context];
@@ -554,7 +571,11 @@
     NSNumber *imageQuality = self.callContext.imageQuality;
     NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality];
 
-    PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info];
+    PHAsset *originalAsset;
+    if (_callContext.requestFullMetadata) {
+      // Full metadata are available only in PHAsset, which requires gallery permission.
+      originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info];
+    }
 
     if (maxWidth != nil || maxHeight != nil) {
       image = [FLTImagePickerImageUtil scaledImage:image
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 64c2045..d73a54d 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
@@ -46,6 +46,9 @@
 /** Maximum number of images to select. 0 indicates no maximum. */
 @property(nonatomic, assign) int maxImageCount;
 
+/** Whether the image should be picked with full metadata (requires gallery permissions) */
+@property(nonatomic, assign) BOOL requestFullMetadata;
+
 @end
 
 #pragma mark -
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
index 310165f..c87bda5 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.0.3), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 #import <Foundation/Foundation.h>
 @protocol FlutterBinaryMessenger;
@@ -45,9 +45,11 @@
 - (void)pickImageWithSource:(FLTSourceSpecification *)source
                     maxSize:(FLTMaxSize *)maxSize
                     quality:(nullable NSNumber *)imageQuality
+               fullMetadata:(NSNumber *)requestFullMetadata
                  completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion;
 - (void)pickMultiImageWithMaxSize:(FLTMaxSize *)maxSize
                           quality:(nullable NSNumber *)imageQuality
+                     fullMetadata:(NSNumber *)requestFullMetadata
                        completion:(void (^)(NSArray<NSString *> *_Nullable,
                                             FlutterError *_Nullable))completion;
 - (void)pickVideoWithSource:(FLTSourceSpecification *)source
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
index 6c91c0a..71a5b51 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.0.3), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 #import "messages.g.h"
 #import <Flutter/Flutter.h>
@@ -144,18 +144,21 @@
         binaryMessenger:binaryMessenger
                   codec:FLTImagePickerApiGetCodec()];
     if (api) {
-      NSCAssert([api respondsToSelector:@selector(pickImageWithSource:maxSize:quality:completion:)],
+      NSCAssert([api respondsToSelector:@selector
+                     (pickImageWithSource:maxSize:quality:fullMetadata:completion:)],
                 @"FLTImagePickerApi api (%@) doesn't respond to "
-                @"@selector(pickImageWithSource:maxSize:quality:completion:)",
+                @"@selector(pickImageWithSource:maxSize:quality:fullMetadata:completion:)",
                 api);
       [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
         NSArray *args = message;
         FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0);
         FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 1);
         NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 2);
+        NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 3);
         [api pickImageWithSource:arg_source
                          maxSize:arg_maxSize
                          quality:arg_imageQuality
+                    fullMetadata:arg_requestFullMetadata
                       completion:^(NSString *_Nullable output, FlutterError *_Nullable error) {
                         callback(wrapResult(output, error));
                       }];
@@ -170,16 +173,19 @@
         binaryMessenger:binaryMessenger
                   codec:FLTImagePickerApiGetCodec()];
     if (api) {
-      NSCAssert([api respondsToSelector:@selector(pickMultiImageWithMaxSize:quality:completion:)],
+      NSCAssert([api respondsToSelector:@selector
+                     (pickMultiImageWithMaxSize:quality:fullMetadata:completion:)],
                 @"FLTImagePickerApi api (%@) doesn't respond to "
-                @"@selector(pickMultiImageWithMaxSize:quality:completion:)",
+                @"@selector(pickMultiImageWithMaxSize:quality:fullMetadata:completion:)",
                 api);
       [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
         NSArray *args = message;
         FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0);
         NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1);
+        NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 2);
         [api pickMultiImageWithMaxSize:arg_maxSize
                                quality:arg_imageQuality
+                          fullMetadata:arg_requestFullMetadata
                             completion:^(NSArray<NSString *> *_Nullable output,
                                          FlutterError *_Nullable error) {
                               callback(wrapResult(output, error));
diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
index 3d1413c..fbc356f 100644
--- a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
+++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
@@ -1,7 +1,6 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-
 import 'dart:async';
 
 import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
@@ -51,24 +50,42 @@
   }) async {
     final String? path = await _pickImageAsPath(
       source: source,
-      maxWidth: maxWidth,
-      maxHeight: maxHeight,
-      imageQuality: imageQuality,
-      preferredCameraDevice: preferredCameraDevice,
+      options: ImagePickerOptions(
+        maxWidth: maxWidth,
+        maxHeight: maxHeight,
+        imageQuality: imageQuality,
+        preferredCameraDevice: preferredCameraDevice,
+      ),
     );
     return path != null ? PickedFile(path) : null;
   }
 
   @override
+  Future<XFile?> getImageFromSource({
+    required ImageSource source,
+    ImagePickerOptions options = const ImagePickerOptions(),
+  }) async {
+    final String? path = await _pickImageAsPath(
+      source: source,
+      options: options,
+    );
+    return path != null ? XFile(path) : null;
+  }
+
+  @override
   Future<List<PickedFile>?> pickMultiImage({
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
   }) async {
     final List<dynamic>? paths = await _pickMultiImageAsPath(
-      maxWidth: maxWidth,
-      maxHeight: maxHeight,
-      imageQuality: imageQuality,
+      options: MultiImagePickerOptions(
+        imageOptions: ImageOptions(
+          maxWidth: maxWidth,
+          maxHeight: maxHeight,
+          imageQuality: imageQuality,
+        ),
+      ),
     );
     if (paths == null) {
       return null;
@@ -77,20 +94,33 @@
     return paths.map((dynamic path) => PickedFile(path as String)).toList();
   }
 
-  Future<List<String>?> _pickMultiImageAsPath({
-    double? maxWidth,
-    double? maxHeight,
-    int? imageQuality,
+  @override
+  Future<List<XFile>> getMultiImageWithOptions({
+    MultiImagePickerOptions options = const MultiImagePickerOptions(),
   }) async {
+    final List<String>? paths = await _pickMultiImageAsPath(options: options);
+    if (paths == null) {
+      return <XFile>[];
+    }
+
+    return paths.map((String path) => XFile(path)).toList();
+  }
+
+  Future<List<String>?> _pickMultiImageAsPath({
+    MultiImagePickerOptions options = const MultiImagePickerOptions(),
+  }) async {
+    final int? imageQuality = options.imageOptions.imageQuality;
     if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
       throw ArgumentError.value(
           imageQuality, 'imageQuality', 'must be between 0 and 100');
     }
 
+    final double? maxWidth = options.imageOptions.maxWidth;
     if (maxWidth != null && maxWidth < 0) {
       throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
     }
 
+    final double? maxHeight = options.imageOptions.maxHeight;
     if (maxHeight != null && maxHeight < 0) {
       throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
     }
@@ -98,22 +128,24 @@
     // TODO(stuartmorgan): Remove the cast once Pigeon supports non-nullable
     //  generics, https://github.com/flutter/flutter/issues/97848
     return (await _hostApi.pickMultiImage(
-            MaxSize(width: maxWidth, height: maxHeight), imageQuality))
+            MaxSize(width: maxWidth, height: maxHeight),
+            imageQuality,
+            options.imageOptions.requestFullMetadata))
         ?.cast<String>();
   }
 
   Future<String?> _pickImageAsPath({
     required ImageSource source,
-    double? maxWidth,
-    double? maxHeight,
-    int? imageQuality,
-    CameraDevice preferredCameraDevice = CameraDevice.rear,
+    ImagePickerOptions options = const ImagePickerOptions(),
   }) {
+    final int? imageQuality = options.imageQuality;
     if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
       throw ArgumentError.value(
           imageQuality, 'imageQuality', 'must be between 0 and 100');
     }
 
+    final double? maxHeight = options.maxHeight;
+    final double? maxWidth = options.maxWidth;
     if (maxWidth != null && maxWidth < 0) {
       throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
     }
@@ -124,10 +156,12 @@
 
     return _hostApi.pickImage(
       SourceSpecification(
-          type: _convertSource(source),
-          camera: _convertCamera(preferredCameraDevice)),
+        type: _convertSource(source),
+        camera: _convertCamera(options.preferredCameraDevice),
+      ),
       MaxSize(width: maxWidth, height: maxHeight),
       imageQuality,
+      options.requestFullMetadata,
     );
   }
 
@@ -167,10 +201,12 @@
   }) async {
     final String? path = await _pickImageAsPath(
       source: source,
-      maxWidth: maxWidth,
-      maxHeight: maxHeight,
-      imageQuality: imageQuality,
-      preferredCameraDevice: preferredCameraDevice,
+      options: ImagePickerOptions(
+        maxWidth: maxWidth,
+        maxHeight: maxHeight,
+        imageQuality: imageQuality,
+        preferredCameraDevice: preferredCameraDevice,
+      ),
     );
     return path != null ? XFile(path) : null;
   }
@@ -182,9 +218,13 @@
     int? imageQuality,
   }) async {
     final List<String>? paths = await _pickMultiImageAsPath(
-      maxWidth: maxWidth,
-      maxHeight: maxHeight,
-      imageQuality: imageQuality,
+      options: MultiImagePickerOptions(
+        imageOptions: ImageOptions(
+          maxWidth: maxWidth,
+          maxHeight: maxHeight,
+          imageQuality: imageQuality,
+        ),
+      ),
     );
     if (paths == null) {
       return null;
diff --git a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
index 0c5859e..5f8768b 100644
--- a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
+++ b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.0.3), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
@@ -115,13 +115,16 @@
   static const MessageCodec<Object?> codec = _ImagePickerApiCodec();
 
   Future<String?> pickImage(SourceSpecification arg_source, MaxSize arg_maxSize,
-      int? arg_imageQuality) async {
+      int? arg_imageQuality, bool arg_requestFullMetadata) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.ImagePickerApi.pickImage', codec,
         binaryMessenger: _binaryMessenger);
-    final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object?>[arg_source, arg_maxSize, arg_imageQuality])
-            as Map<Object?, Object?>?;
+    final Map<Object?, Object?>? replyMap = await channel.send(<Object?>[
+      arg_source,
+      arg_maxSize,
+      arg_imageQuality,
+      arg_requestFullMetadata
+    ]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -140,14 +143,14 @@
     }
   }
 
-  Future<List<String?>?> pickMultiImage(
-      MaxSize arg_maxSize, int? arg_imageQuality) async {
+  Future<List<String?>?> pickMultiImage(MaxSize arg_maxSize,
+      int? arg_imageQuality, bool arg_requestFullMetadata) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.ImagePickerApi.pickMultiImage', codec,
         binaryMessenger: _binaryMessenger);
-    final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object?>[arg_maxSize, arg_imageQuality])
-            as Map<Object?, Object?>?;
+    final Map<Object?, Object?>? replyMap = await channel.send(
+            <Object?>[arg_maxSize, arg_imageQuality, arg_requestFullMetadata])
+        as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart
index 94ac034..dd8a0f0 100644
--- a/packages/image_picker/image_picker_ios/pigeons/messages.dart
+++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart
@@ -35,12 +35,13 @@
 @HostApi(dartHostTestHandler: 'TestHostImagePickerApi')
 abstract class ImagePickerApi {
   @async
-  @ObjCSelector('pickImageWithSource:maxSize:quality:')
-  String? pickImage(
-      SourceSpecification source, MaxSize maxSize, int? imageQuality);
+  @ObjCSelector('pickImageWithSource:maxSize:quality:fullMetadata:')
+  String? pickImage(SourceSpecification source, MaxSize maxSize,
+      int? imageQuality, bool requestFullMetadata);
   @async
-  @ObjCSelector('pickMultiImageWithMaxSize:quality:')
-  List<String>? pickMultiImage(MaxSize maxSize, int? imageQuality);
+  @ObjCSelector('pickMultiImageWithMaxSize:quality:fullMetadata:')
+  List<String>? pickMultiImage(
+      MaxSize maxSize, int? imageQuality, bool requestFullMetadata);
   @async
   @ObjCSelector('pickVideoWithSource:maxDuration:')
   String? pickVideo(SourceSpecification source, int? maxDurationSeconds);
diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml
index dc69513..5c30bf9 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.5+6
+version: 0.8.6
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -19,7 +19,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  image_picker_platform_interface: ^2.3.0
+  image_picker_platform_interface: ^2.6.1
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
index 09517f1..b200257 100644
--- a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
+++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
@@ -39,7 +39,11 @@
 
   @override
   Future<String?> pickImage(
-      SourceSpecification source, MaxSize maxSize, int? imageQuality) async {
+    SourceSpecification source,
+    MaxSize maxSize,
+    int? imageQuality,
+    bool requestFullMetadata,
+  ) async {
     // Flatten arguments for easy comparison.
     calls.add(_LoggedMethodCall('pickImage', arguments: <String, dynamic>{
       'source': source.type,
@@ -47,17 +51,22 @@
       'maxWidth': maxSize.width,
       'maxHeight': maxSize.height,
       'imageQuality': imageQuality,
+      'requestFullMetadata': requestFullMetadata,
     }));
     return returnValue as String?;
   }
 
   @override
   Future<List<String?>?> pickMultiImage(
-      MaxSize maxSize, int? imageQuality) async {
+    MaxSize maxSize,
+    int? imageQuality,
+    bool requestFullMetadata,
+  ) async {
     calls.add(_LoggedMethodCall('pickMultiImage', arguments: <String, dynamic>{
       'maxWidth': maxSize.width,
       'maxHeight': maxSize.height,
       'imageQuality': imageQuality,
+      'requestFullMetadata': requestFullMetadata,
     }));
     return returnValue as List<String?>?;
   }
@@ -103,14 +112,16 @@
             'maxWidth': null,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.gallery,
             'maxWidth': null,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -156,49 +167,56 @@
             'maxWidth': null,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': null,
             'maxHeight': 10.0,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': 20.0,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': null,
             'imageQuality': 70,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': null,
             'maxHeight': 10.0,
             'imageQuality': 70,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': 20.0,
             'imageQuality': 70,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -257,6 +275,7 @@
             'maxHeight': null,
             'imageQuality': null,
             'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -276,6 +295,7 @@
             'maxHeight': null,
             'imageQuality': null,
             'cameraDevice': SourceCamera.front,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -295,6 +315,7 @@
                 'maxWidth': null,
                 'maxHeight': null,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
         ],
       );
@@ -335,42 +356,49 @@
                 'maxWidth': null,
                 'maxHeight': null,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': null,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': null,
                 'maxHeight': 10.0,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': 20.0,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': null,
                 'imageQuality': 70,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': null,
                 'maxHeight': 10.0,
                 'imageQuality': 70,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': 20.0,
                 'imageQuality': 70,
+                'requestFullMetadata': true,
               }),
         ],
       );
@@ -524,14 +552,16 @@
             'maxWidth': null,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.gallery,
             'maxWidth': null,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -577,49 +607,56 @@
             'maxWidth': null,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': null,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': null,
             'maxHeight': 10.0,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': 20.0,
             'imageQuality': null,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': null,
             'imageQuality': 70,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': null,
             'maxHeight': 10.0,
             'imageQuality': 70,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
           const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
             'source': SourceType.camera,
             'maxWidth': 10.0,
             'maxHeight': 20.0,
             'imageQuality': 70,
-            'cameraDevice': SourceCamera.rear
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -678,6 +715,7 @@
             'maxHeight': null,
             'imageQuality': null,
             'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -697,6 +735,7 @@
             'maxHeight': null,
             'imageQuality': null,
             'cameraDevice': SourceCamera.front,
+            'requestFullMetadata': true,
           }),
         ],
       );
@@ -716,6 +755,7 @@
                 'maxWidth': null,
                 'maxHeight': null,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
         ],
       );
@@ -756,42 +796,49 @@
                 'maxWidth': null,
                 'maxHeight': null,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': null,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': null,
                 'maxHeight': 10.0,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': 20.0,
                 'imageQuality': null,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': null,
                 'imageQuality': 70,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': null,
                 'maxHeight': 10.0,
                 'imageQuality': 70,
+                'requestFullMetadata': true,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
                 'maxWidth': 10.0,
                 'maxHeight': 20.0,
                 'imageQuality': 70,
+                'requestFullMetadata': true,
               }),
         ],
       );
@@ -934,4 +981,478 @@
       );
     });
   });
+
+  group('#getImageFromSource', () {
+    test('passes the image source argument correctly', () async {
+      await picker.getImageFromSource(source: ImageSource.camera);
+      await picker.getImageFromSource(source: ImageSource.gallery);
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.gallery,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      await picker.getImageFromSource(source: ImageSource.camera);
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(maxWidth: 10.0),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(maxHeight: 10.0),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+        ),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxWidth: 10.0,
+          imageQuality: 70,
+        ),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxHeight: 10.0,
+          imageQuality: 70,
+        ),
+      );
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options: const ImagePickerOptions(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+          imageQuality: 70,
+        ),
+      );
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': 10.0,
+            'maxHeight': null,
+            'imageQuality': 70,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': null,
+            'maxHeight': 10.0,
+            'imageQuality': 70,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('does not accept a invalid imageQuality argument', () {
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.gallery,
+          options: const ImagePickerOptions(imageQuality: -1),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.gallery,
+          options: const ImagePickerOptions(imageQuality: 101),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(imageQuality: -1),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(imageQuality: 101),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(maxWidth: -1.0),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getImageFromSource(
+          source: ImageSource.camera,
+          options: const ImagePickerOptions(maxHeight: -1.0),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a null image path response gracefully', () async {
+      log.returnValue = null;
+
+      expect(
+          await picker.getImageFromSource(source: ImageSource.gallery), isNull);
+      expect(
+          await picker.getImageFromSource(source: ImageSource.camera), isNull);
+    });
+
+    test('camera position defaults to back', () async {
+      await picker.getImageFromSource(source: ImageSource.camera);
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('camera position can set to front', () async {
+      await picker.getImageFromSource(
+        source: ImageSource.camera,
+        options:
+            const ImagePickerOptions(preferredCameraDevice: CameraDevice.front),
+      );
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.camera,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.front,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('Request full metadata argument defaults to true', () async {
+      await picker.getImageFromSource(source: ImageSource.gallery);
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.gallery,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': true,
+          }),
+        ],
+      );
+    });
+
+    test('passes the request full metadata argument correctly', () async {
+      await picker.getImageFromSource(
+        source: ImageSource.gallery,
+        options: const ImagePickerOptions(requestFullMetadata: false),
+      );
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+            'source': SourceType.gallery,
+            'maxWidth': null,
+            'maxHeight': null,
+            'imageQuality': null,
+            'cameraDevice': SourceCamera.rear,
+            'requestFullMetadata': false,
+          }),
+        ],
+      );
+    });
+  });
+
+  group('#getMultiImageWithOptions', () {
+    test('calls the method correctly', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMultiImageWithOptions();
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': null,
+                'maxHeight': null,
+                'imageQuality': null,
+                'requestFullMetadata': true,
+              }),
+        ],
+      );
+    });
+
+    test('passes the width and height arguments correctly', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMultiImageWithOptions();
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(maxWidth: 10.0),
+        ),
+      );
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(maxHeight: 10.0),
+        ),
+      );
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(maxWidth: 10.0, maxHeight: 20.0),
+        ),
+      );
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(maxWidth: 10.0, imageQuality: 70),
+        ),
+      );
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(maxHeight: 10.0, imageQuality: 70),
+        ),
+      );
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+          ),
+        ),
+      );
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': null,
+                'maxHeight': null,
+                'imageQuality': null,
+                'requestFullMetadata': true,
+              }),
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': 10.0,
+                'maxHeight': null,
+                'imageQuality': null,
+                'requestFullMetadata': true,
+              }),
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': null,
+                'maxHeight': 10.0,
+                'imageQuality': null,
+                'requestFullMetadata': true,
+              }),
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': 10.0,
+                'maxHeight': 20.0,
+                'imageQuality': null,
+                'requestFullMetadata': true,
+              }),
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': 10.0,
+                'maxHeight': null,
+                'imageQuality': 70,
+                'requestFullMetadata': true,
+              }),
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': null,
+                'maxHeight': 10.0,
+                'imageQuality': 70,
+                'requestFullMetadata': true,
+              }),
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': 10.0,
+                'maxHeight': 20.0,
+                'imageQuality': 70,
+                'requestFullMetadata': true,
+              }),
+        ],
+      );
+    });
+
+    test('does not accept a negative width or height argument', () {
+      log.returnValue = <String>['0', '1'];
+      expect(
+        () => picker.getMultiImageWithOptions(
+          options: const MultiImagePickerOptions(
+            imageOptions: ImageOptions(maxWidth: -1.0),
+          ),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMultiImageWithOptions(
+          options: const MultiImagePickerOptions(
+            imageOptions: ImageOptions(maxHeight: -1.0),
+          ),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a invalid imageQuality argument', () {
+      log.returnValue = <String>['0', '1'];
+      expect(
+        () => picker.getMultiImageWithOptions(
+          options: const MultiImagePickerOptions(
+            imageOptions: ImageOptions(imageQuality: -1),
+          ),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMultiImageWithOptions(
+          options: const MultiImagePickerOptions(
+            imageOptions: ImageOptions(imageQuality: 101),
+          ),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('handles a null image path response gracefully', () async {
+      log.returnValue = null;
+
+      expect(await picker.getMultiImageWithOptions(), isEmpty);
+    });
+
+    test('Request full metadata argument defaults to true', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMultiImageWithOptions();
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': null,
+                'maxHeight': null,
+                'imageQuality': null,
+                'requestFullMetadata': true,
+              }),
+        ],
+      );
+    });
+
+    test('Passes the request full metadata argument correctly', () async {
+      log.returnValue = <String>['0', '1'];
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(requestFullMetadata: false),
+        ),
+      );
+
+      expect(
+        log.calls,
+        <_LoggedMethodCall>[
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': null,
+                'maxHeight': null,
+                'imageQuality': null,
+                'requestFullMetadata': false,
+              }),
+        ],
+      );
+    });
+  });
 }
diff --git a/packages/image_picker/image_picker_ios/test/test_api.dart b/packages/image_picker/image_picker_ios/test/test_api.dart
index 8b20f21..1e44f60 100644
--- a/packages/image_picker/image_picker_ios/test/test_api.dart
+++ b/packages/image_picker/image_picker_ios/test/test_api.dart
@@ -1,12 +1,13 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.0.3), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis
 // ignore_for_file: avoid_relative_lib_imports
 // @dart = 2.12
 import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+
 // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316)
 // ignore: unnecessary_import
 import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
@@ -49,9 +50,10 @@
 abstract class TestHostImagePickerApi {
   static const MessageCodec<Object?> codec = _TestHostImagePickerApiCodec();
 
-  Future<String?> pickImage(
-      SourceSpecification source, MaxSize maxSize, int? imageQuality);
-  Future<List<String?>?> pickMultiImage(MaxSize maxSize, int? imageQuality);
+  Future<String?> pickImage(SourceSpecification source, MaxSize maxSize,
+      int? imageQuality, bool requestFullMetadata);
+  Future<List<String?>?> pickMultiImage(
+      MaxSize maxSize, int? imageQuality, bool requestFullMetadata);
   Future<String?> pickVideo(
       SourceSpecification source, int? maxDurationSeconds);
   static void setup(TestHostImagePickerApi? api,
@@ -75,8 +77,11 @@
           assert(arg_maxSize != null,
               'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null MaxSize.');
           final int? arg_imageQuality = (args[2] as int?);
-          final String? output =
-              await api.pickImage(arg_source!, arg_maxSize!, arg_imageQuality);
+          final bool? arg_requestFullMetadata = (args[3] as bool?);
+          assert(arg_requestFullMetadata != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null bool.');
+          final String? output = await api.pickImage(arg_source!, arg_maxSize!,
+              arg_imageQuality, arg_requestFullMetadata!);
           return <Object?, Object?>{'result': output};
         });
       }
@@ -96,8 +101,11 @@
           assert(arg_maxSize != null,
               'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null, expected non-null MaxSize.');
           final int? arg_imageQuality = (args[1] as int?);
-          final List<String?>? output =
-              await api.pickMultiImage(arg_maxSize!, arg_imageQuality);
+          final bool? arg_requestFullMetadata = (args[2] as bool?);
+          assert(arg_requestFullMetadata != null,
+              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null, expected non-null bool.');
+          final List<String?>? output = await api.pickMultiImage(
+              arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!);
           return <Object?, Object?>{'result': output};
         });
       }