Skip to content

Commit 702cc49

Browse files
PiotrMitkowskiAdam Harwood
authored and
Adam Harwood
committed
[image_picker] add requestFullMetadata for iOS (optional permissions) - iOS changes (flutter#5713)
1 parent 498fee8 commit 702cc49

13 files changed

+737
-92
lines changed

packages/image_picker/image_picker_ios/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 0.8.6
22

3+
* Adds `requestFullMetadata` option to `pickImage`, so images on iOS can be picked without `Photo Library Usage` permission.
34
* Updates minimum Flutter version to 2.10.
45

56
## 0.8.5+6

packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m

+49-10
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ - (void)testPluginPickImageDeviceBack {
4646
.andReturn(AVAuthorizationStatusAuthorized);
4747

4848
// Run test
49-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
49+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
5050
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
5151
[plugin setImagePickerControllerOverrides:@[ controller ]];
5252

5353
[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
5454
camera:FLTSourceCameraRear]
5555
maxSize:[[FLTMaxSize alloc] init]
5656
quality:nil
57+
fullMetadata:@(YES)
5758
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
5859
}];
5960

@@ -78,14 +79,15 @@ - (void)testPluginPickImageDeviceFront {
7879
.andReturn(AVAuthorizationStatusAuthorized);
7980

8081
// Run test
81-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
82+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
8283
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
8384
[plugin setImagePickerControllerOverrides:@[ controller ]];
8485

8586
[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
8687
camera:FLTSourceCameraFront]
8788
maxSize:[[FLTMaxSize alloc] init]
8889
quality:nil
90+
fullMetadata:@(YES)
8991
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
9092
}];
9193

@@ -110,7 +112,7 @@ - (void)testPluginPickVideoDeviceBack {
110112
.andReturn(AVAuthorizationStatusAuthorized);
111113

112114
// Run test
113-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
115+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
114116
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
115117
[plugin setImagePickerControllerOverrides:@[ controller ]];
116118

@@ -142,7 +144,7 @@ - (void)testPluginPickVideoDeviceFront {
142144
.andReturn(AVAuthorizationStatusAuthorized);
143145

144146
// Run test
145-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
147+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
146148
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
147149
[plugin setImagePickerControllerOverrides:@[ controller ]];
148150

@@ -165,32 +167,69 @@ - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 {
165167
OCMStub(ClassMethod([photoLibrary authorizationStatus]))
166168
.andReturn(PHAuthorizationStatusAuthorized);
167169

168-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
170+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
169171
[plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
170172

171173
[plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
172174
quality:@(50)
175+
fullMetadata:@(YES)
173176
completion:^(NSArray<NSString *> *_Nullable result,
174177
FlutterError *_Nullable error){
175178
}];
176179
OCMVerify(times(1),
177180
[mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]);
178181
}
179182

183+
- (void)testPickImageWithoutFullMetadata API_AVAILABLE(ios(11)) {
184+
id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
185+
id photoLibrary = OCMClassMock([PHPhotoLibrary class]);
186+
187+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
188+
[plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
189+
190+
[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery
191+
camera:FLTSourceCameraFront]
192+
maxSize:[[FLTMaxSize alloc] init]
193+
quality:nil
194+
fullMetadata:@(NO)
195+
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
196+
}];
197+
198+
OCMVerify(times(0), [photoLibrary authorizationStatus]);
199+
}
200+
201+
- (void)testPickMultiImageWithoutFullMetadata API_AVAILABLE(ios(11)) {
202+
id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
203+
id photoLibrary = OCMClassMock([PHPhotoLibrary class]);
204+
205+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
206+
[plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
207+
208+
[plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init]
209+
quality:nil
210+
fullMetadata:@(NO)
211+
completion:^(NSArray<NSString *> *_Nullable result,
212+
FlutterError *_Nullable error){
213+
}];
214+
215+
OCMVerify(times(0), [photoLibrary authorizationStatus]);
216+
}
217+
180218
#pragma mark - Test camera devices, no op on simulators
181219

182220
- (void)testPluginPickImageDeviceCancelClickMultipleTimes {
183221
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
184222
return;
185223
}
186-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
224+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
187225
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
188226
plugin.imagePickerControllerOverrides = @[ controller ];
189227

190228
[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
191229
camera:FLTSourceCameraRear]
192230
maxSize:[[FLTMaxSize alloc] init]
193231
quality:nil
232+
fullMetadata:@(YES)
194233
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
195234
}];
196235

@@ -202,7 +241,7 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes {
202241
#pragma mark - Test video duration
203242

204243
- (void)testPickingVideoWithDuration {
205-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
244+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
206245
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
207246
[plugin setImagePickerControllerOverrides:@[ controller ]];
208247

@@ -223,12 +262,12 @@ - (void)testViewController {
223262
UIViewController *vc2 = [UIViewController new];
224263
vc1.mockPresented = vc2;
225264

226-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
265+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
227266
XCTAssertEqual([plugin viewControllerWithWindow:window], vc2);
228267
}
229268

230269
- (void)testPluginMultiImagePathHasNullItem {
231-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
270+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
232271

233272
dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
234273
__block FlutterError *pickImageResult = nil;
@@ -245,7 +284,7 @@ - (void)testPluginMultiImagePathHasNullItem {
245284
}
246285

247286
- (void)testPluginMultiImagePathHasItem {
248-
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
287+
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
249288
NSArray *pathList = @[ @"test" ];
250289

251290
dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);

packages/image_picker/image_picker_ios/example/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ dependencies:
1616
# The example app is bundled with the plugin so we use a path dependency on
1717
# the parent directory to use the current plugin's version.
1818
path: ../
19-
image_picker_platform_interface: ^2.3.0
19+
image_picker_platform_interface: ^2.6.1
2020
video_player: ^2.1.4
2121

2222
dev_dependencies:

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

+25-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ @interface FLTImagePickerPlugin () <UINavigationControllerDelegate,
5656
@implementation FLTImagePickerPlugin
5757

5858
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
59-
FLTImagePickerPlugin *instance = [FLTImagePickerPlugin new];
59+
FLTImagePickerPlugin *instance = [[FLTImagePickerPlugin alloc] init];
6060
FLTImagePickerApiSetup(registrar.messenger, instance);
6161
}
6262

@@ -119,7 +119,11 @@ - (void)launchPHPickerWithContext:(nonnull FLTImagePickerMethodCallContext *)con
119119
_pickerViewController.presentationController.delegate = self;
120120
self.callContext = context;
121121

122-
[self checkPhotoAuthorizationForAccessLevel];
122+
if (context.requestFullMetadata) {
123+
[self checkPhotoAuthorizationForAccessLevel];
124+
} else {
125+
[self showPhotoLibraryWithPHPicker:_pickerViewController];
126+
}
123127
}
124128

125129
- (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source
@@ -136,7 +140,16 @@ - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source
136140
camera:[self cameraDeviceForSource:source]];
137141
break;
138142
case FLTSourceTypeGallery:
139-
[self checkPhotoAuthorizationWithImagePicker:imagePickerController];
143+
if (@available(iOS 11, *)) {
144+
if (context.requestFullMetadata) {
145+
[self checkPhotoAuthorizationWithImagePicker:imagePickerController];
146+
} else {
147+
[self showPhotoLibraryWithImagePicker:imagePickerController];
148+
}
149+
} else {
150+
// Prior to iOS 11, accessing gallery requires authorization
151+
[self checkPhotoAuthorizationWithImagePicker:imagePickerController];
152+
}
140153
break;
141154
default:
142155
[self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source"
@@ -151,6 +164,7 @@ - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source
151164
- (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source
152165
maxSize:(nonnull FLTMaxSize *)maxSize
153166
quality:(nullable NSNumber *)imageQuality
167+
fullMetadata:(NSNumber *)fullMetadata
154168
completion:
155169
(nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion {
156170
[self cancelInProgressCall];
@@ -166,6 +180,7 @@ - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source
166180
context.maxSize = maxSize;
167181
context.imageQuality = imageQuality;
168182
context.maxImageCount = 1;
183+
context.requestFullMetadata = [fullMetadata boolValue];
169184

170185
if (source.type == FLTSourceTypeGallery) { // Capture is not possible with PHPicker
171186
if (@available(iOS 14, *)) {
@@ -180,12 +195,14 @@ - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source
180195

181196
- (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize
182197
quality:(nullable NSNumber *)imageQuality
198+
fullMetadata:(NSNumber *)fullMetadata
183199
completion:(nonnull void (^)(NSArray<NSString *> *_Nullable,
184200
FlutterError *_Nullable))completion {
185201
FLTImagePickerMethodCallContext *context =
186202
[[FLTImagePickerMethodCallContext alloc] initWithResult:completion];
187203
context.maxSize = maxSize;
188204
context.imageQuality = imageQuality;
205+
context.requestFullMetadata = [fullMetadata boolValue];
189206

190207
if (@available(iOS 14, *)) {
191208
[self launchPHPickerWithContext:context];
@@ -554,7 +571,11 @@ - (void)imagePickerController:(UIImagePickerController *)picker
554571
NSNumber *imageQuality = self.callContext.imageQuality;
555572
NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality];
556573

557-
PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info];
574+
PHAsset *originalAsset;
575+
if (_callContext.requestFullMetadata) {
576+
// Full metadata are available only in PHAsset, which requires gallery permission.
577+
originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info];
578+
}
558579

559580
if (maxWidth != nil || maxHeight != nil) {
560581
image = [FLTImagePickerImageUtil scaledImage:image

packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ typedef void (^FlutterResultAdapter)(NSArray<NSString *> *_Nullable, FlutterErro
4646
/** Maximum number of images to select. 0 indicates no maximum. */
4747
@property(nonatomic, assign) int maxImageCount;
4848

49+
/** Whether the image should be picked with full metadata (requires gallery permissions) */
50+
@property(nonatomic, assign) BOOL requestFullMetadata;
51+
4952
@end
5053

5154
#pragma mark -

packages/image_picker/image_picker_ios/ios/Classes/messages.g.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v3.0.2), do not edit directly.
4+
// Autogenerated from Pigeon (v3.0.3), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66
#import <Foundation/Foundation.h>
77
@protocol FlutterBinaryMessenger;
@@ -45,9 +45,11 @@ NSObject<FlutterMessageCodec> *FLTImagePickerApiGetCodec(void);
4545
- (void)pickImageWithSource:(FLTSourceSpecification *)source
4646
maxSize:(FLTMaxSize *)maxSize
4747
quality:(nullable NSNumber *)imageQuality
48+
fullMetadata:(NSNumber *)requestFullMetadata
4849
completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion;
4950
- (void)pickMultiImageWithMaxSize:(FLTMaxSize *)maxSize
5051
quality:(nullable NSNumber *)imageQuality
52+
fullMetadata:(NSNumber *)requestFullMetadata
5153
completion:(void (^)(NSArray<NSString *> *_Nullable,
5254
FlutterError *_Nullable))completion;
5355
- (void)pickVideoWithSource:(FLTSourceSpecification *)source

packages/image_picker/image_picker_ios/ios/Classes/messages.g.m

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v3.0.2), do not edit directly.
4+
// Autogenerated from Pigeon (v3.0.3), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66
#import "messages.g.h"
77
#import <Flutter/Flutter.h>
@@ -144,18 +144,21 @@ void FLTImagePickerApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
144144
binaryMessenger:binaryMessenger
145145
codec:FLTImagePickerApiGetCodec()];
146146
if (api) {
147-
NSCAssert([api respondsToSelector:@selector(pickImageWithSource:maxSize:quality:completion:)],
147+
NSCAssert([api respondsToSelector:@selector
148+
(pickImageWithSource:maxSize:quality:fullMetadata:completion:)],
148149
@"FLTImagePickerApi api (%@) doesn't respond to "
149-
@"@selector(pickImageWithSource:maxSize:quality:completion:)",
150+
@"@selector(pickImageWithSource:maxSize:quality:fullMetadata:completion:)",
150151
api);
151152
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
152153
NSArray *args = message;
153154
FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0);
154155
FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 1);
155156
NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 2);
157+
NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 3);
156158
[api pickImageWithSource:arg_source
157159
maxSize:arg_maxSize
158160
quality:arg_imageQuality
161+
fullMetadata:arg_requestFullMetadata
159162
completion:^(NSString *_Nullable output, FlutterError *_Nullable error) {
160163
callback(wrapResult(output, error));
161164
}];
@@ -170,16 +173,19 @@ void FLTImagePickerApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
170173
binaryMessenger:binaryMessenger
171174
codec:FLTImagePickerApiGetCodec()];
172175
if (api) {
173-
NSCAssert([api respondsToSelector:@selector(pickMultiImageWithMaxSize:quality:completion:)],
176+
NSCAssert([api respondsToSelector:@selector
177+
(pickMultiImageWithMaxSize:quality:fullMetadata:completion:)],
174178
@"FLTImagePickerApi api (%@) doesn't respond to "
175-
@"@selector(pickMultiImageWithMaxSize:quality:completion:)",
179+
@"@selector(pickMultiImageWithMaxSize:quality:fullMetadata:completion:)",
176180
api);
177181
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
178182
NSArray *args = message;
179183
FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0);
180184
NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1);
185+
NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 2);
181186
[api pickMultiImageWithMaxSize:arg_maxSize
182187
quality:arg_imageQuality
188+
fullMetadata:arg_requestFullMetadata
183189
completion:^(NSArray<NSString *> *_Nullable output,
184190
FlutterError *_Nullable error) {
185191
callback(wrapResult(output, error));

0 commit comments

Comments
 (0)