Skip to content

Commit

Permalink
feat(ios): allow multiple photo selection (#11867)
Browse files Browse the repository at this point in the history
* add workaround code for video loading
* updated logic for selectionLimit to work with allowMultiple true only

Fixes TIMOB-27984
  • Loading branch information
vijaysingh-axway authored and sgtcoolguy committed Sep 14, 2020
1 parent ef8da1a commit 8b53023
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 6 deletions.
40 changes: 37 additions & 3 deletions apidoc/Titanium/Media/Media.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2054,7 +2054,10 @@ summary: |
properties:
- name: success
summary: Function to call when the photo gallery is closed after a successful selection.
type: Callback<CameraMediaItemType>
description: |
If <Titanium.Media.allowMultiple> is `true`, then Callback<CameraMediaMultipleItemsType> will be invoked.
Otherwise Callback<CameraMediaItemType> will be invoked for single selection.
type: [Callback<CameraMediaItemType>, Callback<CameraMediaMultipleItemsType>]
- name: error
summary: Function to call upon receiving an error.
type: Callback<FailureResponse>
Expand Down Expand Up @@ -2104,8 +2107,18 @@ properties:
description: |
The allowMultiple property is only available on Android API 18 and above.
type: Boolean
platforms: [android]
since: "6.0.0"
platforms: [android, iphone, ipad]
osver: {ios: {min: "14.0"}}
since: { android: "6.0.0", iphone: "9.2.0", ipad: "9.2.0" }
- name: selectionLimit
summary: Specifies number of media item that can be selected.
description: |
Setting this property to zero allows you to select maximum number of media supported by system.
This will work only when <Titanium.Media.allowMultiple> is `true`.
type: Boolean
platforms: [iphone, ipad]
osver: {ios: {min: "14.0"}}
since: "9.2.0"
- name: allowTranscoding
summary: Specifies if the video should be transcoded (using highest quality preset) . If set to false no video transcoding will be performed.
type: Boolean
Expand All @@ -2115,6 +2128,27 @@ properties:
osver: {ios: {min: "11.0"}}

---
name: CameraMediaMultipleItemsType
summary: A media object from photo gallery when <Titanium.Media.allowMultiple> is `true`.
extends: SuccessResponse
properties:
- name: images
summary: |
The list of selected images.
type: Array<CameraMediaItemType>
optional: true
- name: livePhotos
summary: |
The list of selected live photo objects.
type: Array<Titanium.UI.iOS.LivePhoto>
platforms: [iphone, ipad]
optional: true
- name: videos
summary: |
The list of selected videos.
type: Array<CameraMediaItemType>
optional: true
---
name: CameraMediaItemType
summary: A media object from the camera or photo gallery.
extends: SuccessResponse
Expand Down
10 changes: 9 additions & 1 deletion iphone/Classes/MediaModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#if defined(USE_TI_MEDIAGETAPPMUSICPLAYER) || defined(USE_TI_MEDIAOPENMUSICLIBRARY) || defined(USE_TI_MEDIAAPPMUSICPLAYER) || defined(USE_TI_MEDIAGETSYSTEMMUSICPLAYER) || defined(USE_TI_MEDIASYSTEMMUSICPLAYER) || defined(USE_TI_MEDIAHASMUSICLIBRARYPERMISSIONS)
#import <MediaPlayer/MediaPlayer.h>
#endif
#if IS_SDK_IOS_14 && defined(USE_TI_MEDIAOPENPHOTOGALLERY)
#import <PhotosUI/PHPicker.h>
#endif
#import "TiMediaAudioSession.h"
#import "TiMediaMusicPlayer.h"
#import "TiMediaTypes.h"
Expand All @@ -17,12 +20,14 @@
#import <TitaniumKit/TiViewProxy.h>

@class AVAudioRecorder;

@interface MediaModule : TiModule <
UINavigationControllerDelegate,
#if defined(USE_TI_MEDIASHOWCAMERA) || defined(USE_TI_MEDIAOPENPHOTOGALLERY) || defined(USE_TI_MEDIASTARTVIDEOEDITING)
UIImagePickerControllerDelegate,
#endif
#if IS_SDK_IOS_14 && defined(USE_TI_MEDIAOPENPHOTOGALLERY)
PHPickerViewControllerDelegate,
#endif
#ifdef USE_TI_MEDIAOPENMUSICLIBRARY
MPMediaPickerControllerDelegate,
#endif
Expand All @@ -36,6 +41,9 @@
// Camera picker
#if defined(USE_TI_MEDIASHOWCAMERA) || defined(USE_TI_MEDIAOPENPHOTOGALLERY) || defined(USE_TI_MEDIASTARTVIDEOEDITING)
UIImagePickerController *picker;
#endif
#if IS_SDK_IOS_14 && defined(USE_TI_MEDIAOPENPHOTOGALLERY)
PHPickerViewController *_phPicker;
#endif
BOOL autoHidePicker;
BOOL saveToRoll;
Expand Down
192 changes: 190 additions & 2 deletions iphone/Classes/MediaModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#ifdef USE_TI_MEDIAVIDEOPLAYER
#import "TiMediaVideoPlayerProxy.h"
#endif
#import <UniformTypeIdentifiers/UTCoreTypes.h>

// by default, we want to make the camera fullscreen and
// these transform values will scale it when we have our own overlay
Expand Down Expand Up @@ -1107,8 +1108,20 @@ - (void)openPhotoGallery:(id)args
{
ENSURE_SINGLE_ARG_OR_NIL(args, NSDictionary);
ENSURE_UI_THREAD(openPhotoGallery, args);
[self showPicker:args isCamera:NO];

NSArray *types = (NSArray *)[args objectForKey:@"mediaTypes"];
#if IS_SDK_IOS_14
if ([TiUtils isIOSVersionOrGreater:@"14.0"] && [TiUtils boolValue:[args objectForKey:@"allowMultiple"] def:NO]) {
[self showPHPicker:args];
} else {
#endif
[self showPicker:args
isCamera:NO];
#if IS_SDK_IOS_14
}
#endif
}

#endif

/**
Expand Down Expand Up @@ -1378,6 +1391,12 @@ - (void)destroyPicker
}
RELEASE_TO_NIL(picker);
#endif

#if IS_SDK_IOS_14 && defined(USE_TI_MEDIAOPENPHOTOGALLERY)
_phPicker.presentationController.delegate = nil;
RELEASE_TO_NIL(_phPicker);
#endif

#if defined(USE_TI_MEDIASTARTVIDEOEDITING) || defined(USE_TI_MEDIASTOPVIDEOEDITING)
RELEASE_TO_NIL(editor);
#endif
Expand Down Expand Up @@ -1763,6 +1782,175 @@ - (void)handleTrimmedVideo:(NSURL *)theURL withDictionary:(NSDictionary *)dictio
}
#endif

#if IS_SDK_IOS_14
- (void)showPHPicker:(NSDictionary *)args
{
if (_phPicker != nil) {
[self sendPickerError:MediaModuleErrorBusy];
return;
}

animatedPicker = YES;

PHPickerConfiguration *configuration = [[PHPickerConfiguration alloc] init];
NSMutableArray *filterList = [NSMutableArray array];

if (args != nil) {
[self commonPickerSetup:args];

BOOL allowMultiple = [TiUtils boolValue:[args objectForKey:@"allowMultiple"] def:NO];
configuration.selectionLimit = [TiUtils intValue:[args objectForKey:@"selectionLimit"] def:allowMultiple ? 0 : 1];

NSArray *mediaTypes = (NSArray *)[args objectForKey:@"mediaTypes"];
if (mediaTypes) {
for (NSString *mediaType in mediaTypes) {
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
[filterList addObject:PHPickerFilter.imagesFilter];
} else if ([mediaType isEqualToString:(NSString *)kUTTypeLivePhoto]) {
[filterList addObject:PHPickerFilter.livePhotosFilter];
} else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {
[filterList addObject:PHPickerFilter.videosFilter];
}
}
}
}

if (filterList.count == 0) {
[filterList addObject:PHPickerFilter.imagesFilter];
}

PHPickerFilter *filter = [PHPickerFilter anyFilterMatchingSubfilters:filterList];
configuration.filter = filter;

_phPicker = [[PHPickerViewController alloc] initWithConfiguration:configuration];

[_phPicker setDelegate:self];
[self displayModalPicker:_phPicker settings:args];
}

#pragma mark PHPickerViewControllerDelegate

- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results
{
// If user cancels, results count will be 0
if (results.count == 0) {
[self closeModalPicker:picker];
[self sendPickerCancel];
return;
}

dispatch_group_t group = dispatch_group_create();
__block NSMutableArray<NSDictionary *> *imageArray = nil;
__block NSMutableArray<NSDictionary *> *livePhotoArray = nil;
__block NSMutableArray<NSDictionary *> *videoArray = nil;

for (PHPickerResult *result in results) {
dispatch_group_enter(group);

if ([result.itemProvider canLoadObjectOfClass:PHLivePhoto.class]) {
if (!livePhotoArray) {
livePhotoArray = [[NSMutableArray alloc] init];
}
[result.itemProvider loadObjectOfClass:PHLivePhoto.class
completionHandler:^(__kindof id<NSItemProviderReading> _Nullable object, NSError *_Nullable error) {
if (!error) {
TiUIiOSLivePhoto *livePhoto = [[[TiUIiOSLivePhoto alloc] _initWithPageContext:[self pageContext]] autorelease];
[livePhoto setLivePhoto:(PHLivePhoto *)object];
[livePhotoArray addObject:@{@"livePhoto" : livePhoto,
@"mediaType" : (NSString *)kUTTypeLivePhoto,
@"success" : @(YES),
@"code" : @(0)}];
} else {
[livePhotoArray addObject:@{@"error" : error.description,
@"code" : @(error.code),
@"success" : @(NO),
@"mediaType" : (NSString *)kUTTypeLivePhoto}];
DebugLog(@"[ERROR] Failed to load live photo- %@ .", error.description);
}
dispatch_group_leave(group);
}];
} else if ([result.itemProvider canLoadObjectOfClass:UIImage.class]) {
if (!imageArray) {
imageArray = [[NSMutableArray alloc] init];
}
[result.itemProvider loadObjectOfClass:UIImage.class
completionHandler:^(__kindof id<NSItemProviderReading> _Nullable object, NSError *_Nullable error) {
if (!error) {
TiBlob *media = [[[TiBlob alloc] initWithImage:(UIImage *)object] autorelease];
[imageArray addObject:@{@"media" : media,
@"mediaType" : (NSString *)kUTTypeImage,
@"success" : @(YES),
@"code" : @(0)}];
} else {
[imageArray addObject:@{@"error" : error.description,
@"code" : @(error.code),
@"success" : @(NO),
@"mediaType" : (NSString *)kUTTypeImage}];
DebugLog(@"[ERROR] Failed to load image- %@ .", error.description);
}
dispatch_group_leave(group);
}];
} else if ([result.itemProvider hasItemConformingToTypeIdentifier:UTTypeMovie.identifier]) {
if (!videoArray) {
videoArray = [[NSMutableArray alloc] init];
}
[result.itemProvider loadFileRepresentationForTypeIdentifier:UTTypeMovie.identifier
completionHandler:^(NSURL *_Nullable url, NSError *_Nullable error) {
// As per discussion- https://developer.apple.com/forums/thread/652695
if (!error) {
NSString *filename = url.lastPathComponent;
NSFileManager *fileManager = NSFileManager.defaultManager;
NSURL *destPath = [fileManager.temporaryDirectory URLByAppendingPathComponent:filename];

NSError *copyError;

[fileManager copyItemAtURL:url
toURL:destPath
error:&copyError];
TiBlob *media = [[[TiBlob alloc] initWithFile:[destPath path]] autorelease];
if ([media mimeType] == nil) {
[media setMimeType:@"video/mpeg" type:TiBlobTypeFile];
}
[videoArray addObject:@{@"media" : media,
@"mediaType" : (NSString *)kUTTypeMovie,
@"success" : @(YES),
@"code" : @(0)}];
} else {
[videoArray addObject:@{@"error" : error.description,
@"code" : @(error.code),
@"success" : @(NO),
@"mediaType" : (NSString *)kUTTypeMovie}];
DebugLog(@"[ERROR] Failed to load video- %@ .", error.description);
}
dispatch_group_leave(group);
}];
} else {
dispatch_group_leave(group);
NSLog(@"Unsupported media type");
}
}

dispatch_group_notify(group, dispatch_get_global_queue(QOS_CLASS_DEFAULT, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
// Perform completition block
NSMutableDictionary *dictionary = [TiUtils dictionaryWithCode:0 message:nil];
if (livePhotoArray != nil) {
[dictionary setObject:livePhotoArray forKey:@"livePhotos"];
}
if (imageArray != nil) {
[dictionary setObject:imageArray forKey:@"images"];
}
if (videoArray != nil) {
[dictionary setObject:videoArray forKey:@"videos"];
}
[self sendPickerSuccess:dictionary];
});

if (autoHidePicker) {
[self closeModalPicker:picker];
}
}
#endif

#pragma mark UIPopoverPresentationControllerDelegate

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
Expand Down Expand Up @@ -1826,7 +2014,7 @@ - (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationCon
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
#if defined(USE_TI_MEDIASHOWCAMERA) || defined(USE_TI_MEDIAOPENPHOTOGALLERY) || defined(USE_TI_MEDIASTARTVIDEOEDITING)
[self closeModalPicker:picker];
[self closeModalPicker:picker ?: _phPicker];
[self sendPickerCancel];
#endif
}
Expand Down

0 comments on commit 8b53023

Please sign in to comment.