Skip to content

Commit 6dee20e

Browse files
committed
Expand support for different share types
This is a breaking change. The current version of the code doesn't handle public.image correctly in some cases. It assumes that the value returned from getItemForTypeIdentifier is always a NSURL*. This is true for things like Photos, but the app can define what it passes back, while testing, I found that it can also be a UIImage* instance or even NSData*, for example in Notes if you create a note, add a drawing and insert a photo, the drawing will be found as a public.image but it will be in NSData format, not NSURL. In the cases where we get UIImage* or NSData* in order to share them later on we have to have a URL to them on disk, so I used app groups to create a temporary container location to store the files. Then the caller can reference these URLs in the JS code, and finally when we close the share extension we clean up all the temp files to make sure we don't keep growing the size of our disk footprint.
1 parent e1c5ca3 commit 6dee20e

File tree

2 files changed

+147
-47
lines changed

2 files changed

+147
-47
lines changed

ios/ReactNativeShareExtension.m

Lines changed: 144 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
#import "ReactNativeShareExtension.h"
22
#import "React/RCTRootView.h"
33

4-
#define ITEM_IDENTIFIER @"public.url"
5-
#define IMAGE_IDENTIFIER @"public.image"
4+
//public.url
5+
//public.image
6+
//public.movie
7+
//public.plain-text
8+
//#define URL_IDENTIFIER @"public.url"
9+
//#define IMAGE_IDENTIFIER @"public.image"
10+
611

712
NSExtensionContext* extensionContext;
813

@@ -34,16 +39,18 @@ - (void)viewDidLoad {
3439
}
3540

3641

37-
RCT_EXPORT_METHOD(close) {
38-
[extensionContext completeRequestReturningItems:nil
39-
completionHandler:nil];
42+
RCT_EXPORT_METHOD(close:(NSString *)appGroupId) {
43+
[self cleanUpTempFiles:appGroupId];
44+
[extensionContext completeRequestReturningItems:nil
45+
completionHandler:nil];
4046
}
4147

4248
RCT_REMAP_METHOD(data,
43-
resolver:(RCTPromiseResolveBlock)resolve
44-
rejecter:(RCTPromiseRejectBlock)reject)
49+
appGroupId: (NSString *)appGroupId
50+
resolver:(RCTPromiseResolveBlock)resolve
51+
rejecter:(RCTPromiseRejectBlock)reject)
4552
{
46-
[self extractDataFromContext: extensionContext withCallback:^(NSArray* items ,NSException* err) {
53+
[self extractDataFromContext: extensionContext withAppGroup:appGroupId andCallback:^(NSArray* items ,NSException* err) {
4754
if (items == nil) {
4855
resolve(nil);
4956
return;
@@ -53,80 +60,173 @@ - (void)viewDidLoad {
5360
}
5461

5562
RCT_REMAP_METHOD(dataMulti,
63+
appGroupId: (NSString *)appGroupId
5664
resolverMulti:(RCTPromiseResolveBlock)resolve
5765
rejecterMulti:(RCTPromiseRejectBlock)reject)
5866
{
59-
[self extractDataFromContext: extensionContext withCallback:^(NSArray* items ,NSException* err) {
67+
[self extractDataFromContext: extensionContext withAppGroup: appGroupId andCallback:^(NSArray* items ,NSException* err) {
6068
resolve(items);
6169
}];
6270
}
6371

64-
typedef void (^ProviderCallback)(NSURL *url, NSString *contentType, NSException *exception);
72+
typedef void (^ProviderCallback)(NSString *content, NSString *contentType, BOOL owner, NSException *exception);
6573

66-
- (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(NSArray *items ,NSException *exception))callback {
74+
- (void)extractDataFromContext:(NSExtensionContext *)context withAppGroup:(NSString *) appGroupId andCallback:(void(^)(NSArray *items ,NSException *exception))callback {
6775
@try {
6876
NSExtensionItem *item = [context.inputItems firstObject];
6977
NSArray *attachments = item.attachments;
7078
NSMutableArray *items = [[NSMutableArray alloc] init];
71-
79+
7280
__block int attachmentIdx = 0;
7381
__block ProviderCallback providerCb = nil;
74-
providerCb = ^ void (NSURL *url, NSString *contentType, NSException *exception) {
82+
providerCb = ^ void (NSString *content, NSString *contentType, BOOL owner, NSException *exception) {
7583
if (exception) {
7684
callback(nil, exception);
7785
return;
7886
}
79-
80-
[items addObject:@{
81-
@"type": contentType,
82-
@"value": [url absoluteString]
83-
}];
87+
88+
if (content != nil) {
89+
[items addObject:@{
90+
@"type": contentType,
91+
@"value": content,
92+
@"owner": [NSNumber numberWithBool:owner],
93+
}];
94+
}
8495

8596
++attachmentIdx;
8697
if (attachmentIdx == [attachments count]) {
8798
callback(items, nil);
8899
} else {
89-
[self extractDataFromProvider:attachments[attachmentIdx] withCallback: providerCb];
100+
[self extractDataFromProvider:attachments[attachmentIdx] withAppGroup:appGroupId andCallback: providerCb];
90101
}
91102
};
92-
[self extractDataFromProvider:attachments[0] withCallback: providerCb];
103+
[self extractDataFromProvider:attachments[0] withAppGroup:appGroupId andCallback: providerCb];
93104
}
94105
@catch (NSException *exception) {
95106
callback(nil,exception);
96107
}
97108
}
98109

99-
- (void)extractDataFromProvider:(NSItemProvider *)provider withCallback:(void(^)(NSURL* url, NSString* contentType ,NSException *exception))callback {
100-
NSItemProvider *urlProvider = nil;
101-
NSItemProvider *imageProvider = nil;
102-
103-
if([provider hasItemConformingToTypeIdentifier:ITEM_IDENTIFIER]) {
104-
urlProvider = provider;
105-
}else if ([provider hasItemConformingToTypeIdentifier:IMAGE_IDENTIFIER]){
106-
imageProvider = provider;
107-
}
108-
109-
if(urlProvider) {
110-
[urlProvider loadItemForTypeIdentifier:ITEM_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
111-
NSURL *url = (NSURL *)item;
110+
- (void)extractDataFromProvider:(NSItemProvider *)provider withAppGroup:(NSString *) appGroupId andCallback:(void(^)(NSString* content, NSString* contentType, BOOL owner, NSException *exception))callback {
111+
112+
if([provider hasItemConformingToTypeIdentifier:@"public.image"]) {
113+
[provider loadItemForTypeIdentifier:@"public.image" options:nil completionHandler:^(id<NSSecureCoding, NSObject> item, NSError *error) {
114+
if (error) {
115+
callback(nil, nil, NO, error);
116+
return;
117+
}
118+
119+
if ([item isKindOfClass: NSURL.class]) {
120+
NSURL *url = (NSURL *)item;
121+
return callback([url absoluteString], @"public.image", NO, nil);
122+
} else if ([item isKindOfClass: UIImage.class]) {
123+
UIImage *image = (UIImage *)item;
124+
NSString *fileName = [NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID] UUIDString]];
125+
NSURL *tempContainerURL = [ReactNativeShareExtension tempContainerURL:appGroupId];
126+
if (tempContainerURL == nil){
127+
return callback(nil, nil, NO, nil);
128+
}
112129

113-
if(callback) {
114-
callback(url,@"text/plain" ,nil);
130+
NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName];
131+
BOOL created = [UIImageJPEGRepresentation(image, 0.95) writeToFile:[tempFileURL path] atomically:YES];
132+
if (created) {
133+
return callback([tempFileURL absoluteString], @"public.image", YES, nil);
134+
} else {
135+
return callback(nil, nil, NO, nil);
136+
}
137+
} else if ([item isKindOfClass: NSData.class]) {
138+
NSString *fileName = [NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID] UUIDString]];
139+
NSData *data = (NSData *)item;
140+
UIImage *image = [UIImage imageWithData:data];
141+
NSURL *tempContainerURL = [ReactNativeShareExtension tempContainerURL:appGroupId];
142+
if (tempContainerURL == nil){
143+
return callback(nil, nil, NO, nil);
144+
}
145+
NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName];
146+
BOOL created = [UIImageJPEGRepresentation(image, 0.95) writeToFile:[tempFileURL path] atomically:YES];
147+
if (created) {
148+
return callback([tempFileURL absoluteString], @"public.image", YES, nil);
149+
} else {
150+
return callback(nil, nil, NO, nil);
151+
}
152+
} else {
153+
// Do nothing, some type we don't support.
154+
return callback(nil, nil, NO, nil);
115155
}
116156
}];
117-
}else if (imageProvider){
118-
[imageProvider loadItemForTypeIdentifier:IMAGE_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
119-
NSURL *url = (NSURL *)item;
120-
121-
if(callback) {
122-
callback(url,[[[url absoluteString] pathExtension] lowercaseString] ,nil);
157+
return;
158+
}
159+
160+
if([provider hasItemConformingToTypeIdentifier:@"public.url"]) {
161+
[provider loadItemForTypeIdentifier:@"public.url" options:nil completionHandler:^(id<NSSecureCoding, NSObject> item, NSError *error) {
162+
if (error) {
163+
callback(nil, nil, NO, error);
164+
return;
165+
}
166+
callback([(NSURL *)item absoluteString], @"public.url", NO, nil);
167+
}];
168+
return;
169+
}
170+
171+
if([provider hasItemConformingToTypeIdentifier:@"public.text"]) {
172+
[provider loadItemForTypeIdentifier:@"public.text" options:nil completionHandler:^(id<NSSecureCoding, NSObject> item, NSError *error) {
173+
if (error) {
174+
callback(nil, nil, NO, error);
175+
return;
176+
}
177+
178+
if ([item isKindOfClass:NSString.class]) {
179+
return callback((NSString *)item, @"public.text", NO, nil);
180+
} else if ([item isKindOfClass:NSData.class]) {
181+
NSString *str = [[NSString alloc] initWithData:(NSData *)item encoding:NSUTF8StringEncoding];
182+
if (str) {
183+
return callback(str, @"public.text", NO, nil);
184+
} else {
185+
return callback(nil, nil, NO, nil);
186+
}
187+
} else {
188+
return callback(nil, nil, NO, nil);
123189
}
124190
}];
125-
} else {
126-
if(callback) {
127-
callback(nil, nil,[NSException exceptionWithName:@"Error" reason:@"couldn't find provider" userInfo:nil]);
191+
return;
192+
}
193+
194+
callback(nil, nil, NO, nil);
195+
}
196+
197+
+ (NSURL*) tempContainerURL: (NSString*)appGroupId {
198+
NSFileManager *manager = [NSFileManager defaultManager];
199+
NSURL *containerURL = [manager containerURLForSecurityApplicationGroupIdentifier: appGroupId];
200+
NSURL *tempDirectoryURL = [containerURL URLByAppendingPathComponent:@"shareTempItems"];
201+
if (![manager fileExistsAtPath:[tempDirectoryURL path]]) {
202+
NSError *err;
203+
[manager createDirectoryAtURL:tempDirectoryURL withIntermediateDirectories:YES attributes:nil error:&err];
204+
if (err) {
205+
return nil;
128206
}
129207
}
208+
209+
return tempDirectoryURL;
210+
}
211+
212+
- (void) cleanUpTempFiles:(NSString *)appGroupId {
213+
NSURL *tmpDirectoryURL = [ReactNativeShareExtension tempContainerURL:appGroupId];
214+
if (tmpDirectoryURL == nil) {
215+
return;
216+
}
217+
218+
NSFileManager *fileManager = [NSFileManager defaultManager];
219+
NSError *error;
220+
NSArray *tmpFiles = [fileManager contentsOfDirectoryAtPath:[tmpDirectoryURL path] error:&error];
221+
if (error) {
222+
return;
223+
}
224+
225+
for (NSString *file in tmpFiles)
226+
{
227+
error = nil;
228+
[fileManager removeItemAtPath:[[tmpDirectoryURL URLByAppendingPathComponent:file] path] error:&error];
229+
}
130230
}
131231

132232
@end

lib/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NativeModules } from 'react-native'
44
// const { type, value } = await NativeModules.ShareExtension.data()
55
// NativeModules.ShareExtension.close()
66
export default {
7-
data: () => NativeModules.ReactNativeShareExtension.data(),
8-
dataMulti: () => NativeModules.ReactNativeShareExtension.dataMulti(),
9-
close: () => NativeModules.ReactNativeShareExtension.close()
7+
data: (appGroupId) => NativeModules.ReactNativeShareExtension.data(appGroupId),
8+
dataMulti: (appGroupId) => NativeModules.ReactNativeShareExtension.dataMulti(appGroupId),
9+
close: (appGroupId) => NativeModules.ReactNativeShareExtension.close(appGroupId)
1010
}

0 commit comments

Comments
 (0)