Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mac os support #612

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ If you want to ask questions, we opened [GH discussions](https://github.com/rnmo

A React Native wrapper for:

- Apple's `UIDocumentPickerViewController`
- iOS `UIDocumentPickerViewController`
- OSX `NSOpenPanel`
- Android's `Intent.ACTION_GET_CONTENT`
- Windows `Windows.Storage.Pickers`

Expand All @@ -18,7 +19,7 @@ A React Native wrapper for:
</tr>
</table>

Requires RN >= 0.63, Android 5.0+ and iOS 11+
Requires RN >= 0.63, Android 5.0+, iOS 11+ and OSX 10.5

# Table of Contents

Expand Down
6 changes: 5 additions & 1 deletion ios/RCTConvert+RNDocumentPicker.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#import <React/RCTConvert.h>

#if __has_include(<UIKit/UIKit.h>)
@import UIKit;
#endif

@interface RCTConvert(RNDocumentPicker)

#if __has_include(<UIKit/UIKit.h>)
+ (UIModalPresentationStyle)UIModalPresentationStyle:(NSString*)value;
+ (UIModalTransitionStyle)UIModalTransitionStyle:(NSString*)value;

#endif
@end
3 changes: 3 additions & 0 deletions ios/RCTConvert+RNDocumentPicker.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import "RCTConvert+RNDocumentPicker.h"

#if __has_include(<UIKit/UIKit.h>)
@implementation RCTConvert (RNDocumentPicker)
// TODO how to de-duplicate from https://github.com/facebook/react-native/blob/v0.66.0/React/Views/RCTModalHostViewManager.m?
RCT_ENUM_CONVERTER(
Expand All @@ -26,3 +27,5 @@ @implementation RCTConvert (RNDocumentPicker)
integerValue)

@end

#endif
2 changes: 2 additions & 0 deletions ios/RNDocumentPicker.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#import <React/RCTBridgeModule.h>

#if __has_include(<UIKit/UIKit.h>)
@import UIKit;
#endif

@interface RNDocumentPicker : NSObject <RCTBridgeModule>

Expand Down
127 changes: 84 additions & 43 deletions ios/RNDocumentPicker.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#import "RNDocumentPicker.h"

#if __has_include(<UIKit/UIKit.h>)
#import <MobileCoreServices/MobileCoreServices.h>
#endif

#import <React/RCTConvert.h>
#import <React/RCTBridge.h>
Expand All @@ -22,11 +24,19 @@
static NSString *const FIELD_SIZE = @"size";


#if __has_include(<UIKit/UIKit.h>)
@interface RNDocumentPicker () <UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate>
@end
#else
@interface RNDocumentPicker ()
@end

#endif

@implementation RNDocumentPicker {
#if __has_include(<UIKit/UIKit.h>)
UIDocumentPickerMode mode;
#endif
NSString *copyDestination;
RNCPromiseWrapper* promiseWrapper;
NSMutableArray *urlsInOpenMode;
Expand Down Expand Up @@ -66,53 +76,59 @@ - (dispatch_queue_t)methodQueue
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
mode = options[@"mode"] && [options[@"mode"] isEqualToString:@"open"] ? UIDocumentPickerModeOpen : UIDocumentPickerModeImport;

copyDestination = options[@"copyTo"];
UIModalPresentationStyle presentationStyle = [RCTConvert UIModalPresentationStyle:options[@"presentationStyle"]];
UIModalTransitionStyle transitionStyle = [RCTConvert UIModalTransitionStyle:options[@"transitionStyle"]];
[promiseWrapper setPromiseWithInProgressCheck:resolve rejecter:reject fromCallSite:@"pick"];

NSArray *allowedUTIs = [RCTConvert NSArray:options[OPTION_TYPE]];
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:allowedUTIs inMode:mode];

documentPicker.modalPresentationStyle = presentationStyle;
documentPicker.modalTransitionStyle = transitionStyle;

documentPicker.delegate = self;
documentPicker.presentationController.delegate = self;

documentPicker.allowsMultipleSelection = [RCTConvert BOOL:options[OPTION_MULTIPLE]];

UIViewController *rootViewController = RCTPresentedViewController();

[rootViewController presentViewController:documentPicker animated:YES completion:nil];
}


- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
NSMutableArray *results = [NSMutableArray array];
for (id url in urls) {
NSError *error;
NSMutableDictionary *result = [self getMetadataForUrl:url error:&error];
if (result) {
[results addObject:result];
} else {
[promiseWrapper reject:E_INVALID_DATA_RETURNED withError:error];
return;
#if __has_include(<UIKit/UIKit.h>)
mode = options[@"mode"] && [options[@"mode"] isEqualToString:@"open"] ? UIDocumentPickerModeOpen : UIDocumentPickerModeImport;
UIModalPresentationStyle presentationStyle = [RCTConvert UIModalPresentationStyle:options[@"presentationStyle"]];
UIModalTransitionStyle transitionStyle = [RCTConvert UIModalTransitionStyle:options[@"transitionStyle"]];
[promiseWrapper setPromiseWithInProgressCheck:resolve rejecter:reject fromCallSite:@"pick"];

UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:allowedUTIs inMode:mode];

documentPicker.modalPresentationStyle = presentationStyle;
documentPicker.modalTransitionStyle = transitionStyle;

documentPicker.delegate = self;
documentPicker.presentationController.delegate = self;

UIViewController *rootViewController = RCTPresentedViewController();

[rootViewController presentViewController:documentPicker animated:YES completion:nil];
#else
[promiseWrapper setPromiseWithInProgressCheck:resolve rejecter:reject fromCallSite:@"pick"];
NSOpenPanel *op = [NSOpenPanel openPanel];
op.canChooseFiles = YES;
op.canChooseDirectories = YES;
op.allowsMultipleSelection = [RCTConvert BOOL:options[OPTION_MULTIPLE]];
[op runModal];

NSMutableArray *results = [NSMutableArray array];
for (id url in op.URLs) {
NSError *error;
NSMutableDictionary *result = [self getMetadataForUrl:url error:&error];
if (result) {
[results addObject:result];
} else {
[promiseWrapper reject:E_INVALID_DATA_RETURNED withError:error];
return;
}
}
}

[promiseWrapper resolve:results];
[promiseWrapper resolve:results];
#endif
}

- (NSMutableDictionary *)getMetadataForUrl:(NSURL *)url error:(NSError **)error
{
__block NSMutableDictionary *result = [NSMutableDictionary dictionary];

if (mode == UIDocumentPickerModeOpen) {
[urlsInOpenMode addObject:url];
}
#if __has_include(<UIKit/UIKit.h>)
if (mode == UIDocumentPickerModeOpen) {
[urlsInOpenMode addObject:url];
}
#endif

// TODO handle error
[url startAccessingSecurityScopedResource];
Expand All @@ -123,8 +139,8 @@ - (NSMutableDictionary *)getMetadataForUrl:(NSURL *)url error:(NSError **)error
// TODO double check this implemenation, see eg. https://developer.apple.com/documentation/foundation/nsfilecoordinator/1412420-prepareforreadingitemsaturls
[coordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingResolvesSymbolicLink error:&fileError byAccessor:^(NSURL *newURL) {
// If the coordinated operation fails, then the accessor block never runs
result[FIELD_URI] = ((mode == UIDocumentPickerModeOpen) ? url : newURL).absoluteString;

result[FIELD_URI] = url.absoluteString;
NSError *copyError;
NSString *maybeFileCopyPath = copyDestination ? [RNDocumentPicker copyToUniqueDestinationFrom:newURL usingDestinationPreset:copyDestination error:copyError].absoluteString : nil;

Expand All @@ -134,7 +150,7 @@ - (NSMutableDictionary *)getMetadataForUrl:(NSURL *)url error:(NSError **)error
result[FIELD_COPY_ERR] = copyError.localizedDescription;
result[FIELD_FILE_COPY_URI] = [NSNull null];
}

result[FIELD_NAME] = newURL.lastPathComponent;

NSError *attributesError = nil;
Expand All @@ -160,10 +176,12 @@ - (NSMutableDictionary *)getMetadataForUrl:(NSURL *)url error:(NSError **)error
result[FIELD_TYPE] = [NSNull null];
}
}];

if (mode != UIDocumentPickerModeOpen) {
[url stopAccessingSecurityScopedResource];
}

#if __has_include(<UIKit/UIKit.h>)
if (mode != UIDocumentPickerModeOpen) {
[url stopAccessingSecurityScopedResource];
}
#endif

if (fileError) {
*error = fileError;
Expand All @@ -173,6 +191,26 @@ - (NSMutableDictionary *)getMetadataForUrl:(NSURL *)url error:(NSError **)error
}
}

#if __has_include(<UIKit/UIKit.h>)
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
NSMutableArray *results = [NSMutableArray array];
for (id url in urls) {
NSError *error;
NSMutableDictionary *result = [self getMetadataForUrl:url error:&error];
if (result) {
[results addObject:result];
} else {
[promiseWrapper reject:E_INVALID_DATA_RETURNED withError:error];
return;
}
}

[promiseWrapper resolve:results];
}
#endif


RCT_EXPORT_METHOD(releaseSecureAccess:(NSArray<NSString *> *)uris
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
Expand Down Expand Up @@ -222,6 +260,7 @@ + (NSURL *)getDirectoryForFileCopy:(NSString *)copyToDirectory
return [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
}

#if __has_include(<UIKit/UIKit.h>)
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller
{
[self rejectAsUserCancellationError];
Expand All @@ -231,6 +270,7 @@ - (void)presentationControllerDidDismiss:(UIPresentationController *)presentatio
{
[self rejectAsUserCancellationError];
}
#endif

- (void)rejectAsUserCancellationError
{
Expand All @@ -240,3 +280,4 @@ - (void)rejectAsUserCancellationError
}

@end

5 changes: 4 additions & 1 deletion react-native-document-picker.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ Pod::Spec.new do |s|
s.license = package["license"]
s.authors = package["author"]

s.platforms = { :ios => "11.0" }
s.ios.deployment_target = '11.0'
# s.tvos.deployment_target = '9.2'
s.osx.deployment_target = '10.5'

s.source = { :git => "https://github.com/rnmods/react-native-document-picker.git", :tag => "v#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm}"
Expand Down