Skip to content
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
30 changes: 30 additions & 0 deletions example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,33 @@ const App = () => {
}
};

/**
* Share url with activityItemSources for custom link metadata
*/
const shareUrlWithMetadata = async () => {
const url = 'https://github.com/react-native-share/react-native-share';
const shareOptions = {
url,
activityItemSources: [
{
placeholderItem: { type: 'url', content: url },
item: { default: { type: 'url', content: url } },
linkMetadata: { title: 'A Custom Share Title' }
}
],
failOnCancel: false
};

try {
const ShareResponse = await Share.open(shareOptions);
console.log('Result =>', ShareResponse);
setResult(JSON.stringify(ShareResponse, null, 2));
} catch (error) {
console.log('Error =>', error);
setResult('error: '.concat(getErrorString(error)));
}
};

/**
* This functions share multiple images that
* you send as the urls param
Expand Down Expand Up @@ -402,6 +429,9 @@ const App = () => {
<View style={styles.button}>
<Button onPress={shareUrlWithMessage} title="Share Simple Url" />
</View>
<View style={styles.button}>
<Button onPress={shareUrlWithMetadata} title="Share Url with Metadata" />
</View>
<View style={styles.button}>
<Button onPress={shareMultipleImages} title="Share Multiple Images" />
</View>
Expand Down
143 changes: 93 additions & 50 deletions ios/RNShare.mm
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,9 @@ - (NSDictionary*) getConstants {
}

NSArray *activityItemSources = options[@"activityItemSources"];
if (activityItemSources) {
[activityItemSources enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
RNShareActivityItemSource *activityItemSource = [[RNShareActivityItemSource alloc] initWithOptions:obj];
[items addObject:activityItemSource];
}];
}
BOOL hasActivityItemSources = activityItemSources != nil && activityItemSources.count > 0;

if (items.count == 0) {
if (items.count == 0 && !hasActivityItemSources) {
RCTLogError(@"No `url` or `message` to share");
return;
}
Expand Down Expand Up @@ -290,18 +285,76 @@ - (NSDictionary*) getConstants {
[controller presentViewController:documentPicker animated:YES completion:nil];
return;
}
}

}

if (hasActivityItemSources) {
[self _fetchMetadataAndPresentShareController:items
options:options
controller:controller
resolve:resolve
reject:reject];
} else {
[self _presentShareController:items
options:options
controller:controller
resolve:resolve
reject:reject];
}
}


RCT_EXPORT_METHOD(isBase64File:(NSString *)url
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
NSError *err = [NSError errorWithDomain:@"NOT IMPLEMENTED" code: 500
userInfo:@{NSLocalizedDescriptionKey:@"isBase64File is not implemented for iOS"}];
reject(@"NOT IMPLEMENTED",@"NOT IMPLEMENTED",err);
}


RCT_EXPORT_METHOD(isPackageInstalled:(NSString *)packagename
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
NSError *err = [NSError errorWithDomain:@"NOT IMPLEMENTED" code: 500
userInfo:@{NSLocalizedDescriptionKey:@"isPackageInstalled is not implemented for iOS"}];
reject(@"NOT IMPLEMENTED",@"NOT IMPLEMENTED",err);
}

- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
if (rejectBlock) {
NSError *error = [NSError errorWithDomain:@"CANCELLED" code: 500 userInfo:@{NSLocalizedDescriptionKey:@"PICKER_WAS_CANCELLED"}];
rejectBlock(@"CANCELLED",@"CANCELLED",error);
}
}

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
if (resolveBlock) {
resolveBlock(@{
@"success": @(YES),
@"message": @"com.apple.DocumentsApp"
});
}
}

#pragma mark - Share Controller

- (void)_presentShareController:(NSArray *)items
options:(NSDictionary *)options
controller:(UIViewController *)controller
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject
{
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];

BOOL disableOverlay = [RCTConvert BOOL:options[@"disableOverlay"]];

if (@available(iOS 15.0, *)) {
if (disableOverlay == true) {
shareController.sheetPresentationController.largestUndimmedDetentIdentifier = UISheetPresentationControllerDetentIdentifierLarge;
shareController.sheetPresentationController.largestUndimmedDetentIdentifier = UISheetPresentationControllerDetentIdentifierLarge;
}
}

NSString *subject = [RCTConvert NSString:options[@"subject"]];
if (subject) {
[shareController setValue:subject forKey:@"subject"];
Expand All @@ -314,7 +367,6 @@ - (NSDictionary*) getConstants {

__weak UIActivityViewController* weakShareController = shareController;
shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {

// always dismiss since this may be called from cancelled shares
// but the share menu would remain open, and our callback would fire again on close
if(weakShareController){
Expand All @@ -324,7 +376,6 @@ - (NSDictionary*) getConstants {
[controller dismissViewControllerAnimated:true completion:nil];
}


if (activityError) {
reject(@"error",@"activityError",activityError);
} else {
Expand All @@ -333,7 +384,7 @@ - (NSDictionary*) getConstants {
@"message": (RCTNullIfNil(activityType) ?: @"")
});
}

// clear the completion handler to prevent cycles
if(weakShareController){
weakShareController.completionWithItemsHandler = nil;
Expand All @@ -346,47 +397,39 @@ - (NSDictionary*) getConstants {
shareController.popoverPresentationController.permittedArrowDirections = 0;
}
shareController.popoverPresentationController.sourceView = controller.view;
shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view anchorViewTag:anchorViewTag];
shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view
anchorViewTag:anchorViewTag];

[controller presentViewController:shareController animated:YES completion:nil];

shareController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
}


RCT_EXPORT_METHOD(isBase64File:(NSString *)url
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
NSError *err = [NSError errorWithDomain:@"NOT IMPLEMENTED" code: 500
userInfo:@{NSLocalizedDescriptionKey:@"isBase64File is not implemented for iOS"}];
reject(@"NOT IMPLEMENTED",@"NOT IMPLEMENTED",err);
}


RCT_EXPORT_METHOD(isPackageInstalled:(NSString *)packagename
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
- (void)_fetchMetadataAndPresentShareController:(NSMutableArray *)items
options:(NSDictionary *)options
controller:(UIViewController *)controller
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject
{
NSError *err = [NSError errorWithDomain:@"NOT IMPLEMENTED" code: 500
userInfo:@{NSLocalizedDescriptionKey:@"isPackageInstalled is not implemented for iOS"}];
reject(@"NOT IMPLEMENTED",@"NOT IMPLEMENTED",err);
}

- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
if (rejectBlock) {
NSError *error = [NSError errorWithDomain:@"CANCELLED" code: 500 userInfo:@{NSLocalizedDescriptionKey:@"PICKER_WAS_CANCELLED"}];
rejectBlock(@"CANCELLED",@"CANCELLED",error);
}
}

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
if (resolveBlock) {
resolveBlock(@{
@"success": @(YES),
@"message": @"com.apple.DocumentsApp"
});
}
NSArray *activityItemSources = options[@"activityItemSources"];
__block NSInteger pendingFetches = activityItemSources.count;
__weak RNShare *weakSelf = self;

[activityItemSources enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
RNShareActivityItemSource *activityItemSource = [[RNShareActivityItemSource alloc] initWithOptions:obj
completion:^{
pendingFetches--;
if (pendingFetches == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf _presentShareController:items
options:options
controller:controller
resolve:resolve
reject:reject];
});
}
}];
[items addObject:activityItemSource];
}];
}

# pragma mark - New Architecture
Expand Down
3 changes: 2 additions & 1 deletion ios/RNShareActivityItemSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ NS_ASSUME_NONNULL_BEGIN

@interface RNShareActivityItemSource : NSObject<UIActivityItemSource>

- (instancetype)initWithOptions:(NSDictionary *)options;
- (instancetype)initWithOptions:(NSDictionary *)options
completion:(void (^)(void))completion;

@end

Expand Down
62 changes: 37 additions & 25 deletions ios/RNShareActivityItemSource.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ @implementation RNShareActivityItemSource {
NSDictionary *subjectDictionary;
NSDictionary *dataTypeIdentifierDictionary;
NSDictionary *thumbnailImageDictionary;
void (^fetchCompletion)(void);
#ifdef __IPHONE_13_0
LPLinkMetadata *linkMetadata API_AVAILABLE(ios(13.0));
#endif
}

- (instancetype)initWithOptions:(NSDictionary *)options {
- (instancetype)initWithOptions:(NSDictionary *)options
completion:(void (^)(void))completion {
self = [super init];
if (self) {
fetchCompletion = completion;
placeholderItem = [RNShareActivityItemSource itemFromDictionary:options[@"placeholderItem"]];

#ifdef __IPHONE_13_0
Expand All @@ -32,8 +35,14 @@ - (instancetype)initWithOptions:(NSDictionary *)options {
if ([placeholderItem isKindOfClass:NSURL.class] && ![RNShareActivityItemSource isURLSchemeData:placeholderItem]) {
NSURL *URL = placeholderItem;
[self fetchMetadataForURL:URL];
} else {
[self _invokeAndClearCompletion];
}
} else {
[self _invokeAndClearCompletion];
}
#else
[self _invokeAndClearCompletion];
#endif

itemDictionary = options[@"item"];
Expand All @@ -49,28 +58,40 @@ - (void)fetchMetadataForURL:(nonnull NSURL *)URL {
if (@available(iOS 13.0, *)) {
LPMetadataProvider *metadataProvider = [[LPMetadataProvider alloc] init];
[metadataProvider startFetchingMetadataForURL:URL completionHandler:^(LPLinkMetadata * _Nullable metadata, NSError * _Nullable error) {
if (!self->linkMetadata) {
self->linkMetadata = metadata;
} else {
self->linkMetadata.originalURL = metadata.originalURL;
self->linkMetadata.URL = metadata.URL;
if(!self->linkMetadata.title) {
self->linkMetadata.title = metadata.title;
}
self->linkMetadata.imageProvider = metadata.imageProvider;
if (self->linkMetadata.imageProvider) {
self->linkMetadata.iconProvider = self->linkMetadata.imageProvider;
if (metadata) {
if (!self->linkMetadata) {
self->linkMetadata = metadata;
} else {
self->linkMetadata.iconProvider = metadata.iconProvider;
self->linkMetadata.originalURL = metadata.originalURL;
self->linkMetadata.URL = metadata.URL;
if(!self->linkMetadata.title) {
self->linkMetadata.title = metadata.title;
}
self->linkMetadata.imageProvider = metadata.imageProvider;
if (self->linkMetadata.imageProvider) {
self->linkMetadata.iconProvider = self->linkMetadata.imageProvider;
} else {
self->linkMetadata.iconProvider = metadata.iconProvider;
}
self->linkMetadata.remoteVideoURL = metadata.remoteVideoURL;
self->linkMetadata.videoProvider = metadata.videoProvider;
}
self->linkMetadata.remoteVideoURL = metadata.remoteVideoURL;
self->linkMetadata.videoProvider = metadata.videoProvider;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self _invokeAndClearCompletion];
});
}];
}
#endif
}

- (void)_invokeAndClearCompletion {
if (fetchCompletion) {
fetchCompletion();
fetchCompletion = nil;
}
}

#pragma mark - Utilities

+ (BOOL)isURLSchemeData:(nullable NSURL *)URL {
Expand Down Expand Up @@ -276,16 +297,7 @@ - (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activity
- (nullable id)activityViewController:(nonnull UIActivityViewController *)activityViewController itemForActivityType:(nullable UIActivityType)activityType {
if (itemDictionary) {
NSDictionary *options = [RNShareActivityItemSource objectForActivityType:activityType inDictionary:itemDictionary];
id item = [RNShareActivityItemSource itemFromDictionary:options];

if (@available(iOS 13.0, *)) {
if ([item isKindOfClass:NSURL.class] && ![RNShareActivityItemSource isURLSchemeData:item]) {
NSURL *URL = item;
[self fetchMetadataForURL:URL];
}
}

return item;
return [RNShareActivityItemSource itemFromDictionary:options];
}
return nil;
}
Expand Down