alinz/react-native-share-extension

iOS News app doesn't give the URL in data()

Opened this issue · 3 comments

I got everything working so I can share links from Safari, but when I share from the Apple News app, await ShareExtension.data() only returns {"type": "text/plain", "value": (the title of the news article)}. I'm using the same info.plist I used in a native app I wrote before that had a share extension that worked with News and Safari alike.

The native code I used in that other app:

   func getUrl(callback: @escaping ((URL?) -> ())) {
        guard let items = extensionContext?.inputItems,
            let item = items.first as? NSExtensionItem,
            let attachments = item.attachments else {
                callback(nil)
                return
        }
        var found = false
        for attachment in attachments {
            if let provider = attachment as? NSItemProvider {
                if provider.hasItemConformingToTypeIdentifier("public.url") {
                    found = true
                    provider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
                        if let shareURL = url as? URL {
                            callback(shareURL)
                        } else {
                            print("error getting url: \(error)")
                            callback(nil)
                        }
                    }
                }
            }
        }
        if !found {
            callback(nil)
            return
        }
    }

The Plist:

	<key>NSExtension</key>
	<dict>
		<key>NSExtensionActivationDictionaryVersion</key>
		<integer>2</integer>
		<key>NSExtensionActivationUsesStrictMatching</key>
		<integer>2</integer>
		<key>NSExtensionAttributes</key>
		<dict>
			<key>NSExtensionActivationRule</key>
			<string>SUBQUERY(extensionItems, $e, (
            SUBQUERY($e.attachments, $a, ANY $a.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url").@count == 1
            )).@count == 1
          </string>
			<key>RequestsOpenAccess</key>
			<true/>
		</dict>
		<key>NSExtensionMainStoryboard</key>
		<string>MainInterface</string>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.share-services</string>
	</dict>

@tolmekian1453 not an answer for your question, but I am still struggling to get working, I have followed all the steps in the installing guide, the share extension is there in the share menu, but when I click on it nothing happens at all. Could you please tell me if there is any further steps that you have done to get it working?

I got it to work by editing the code to return values from every provider, not just the first one that has data. It's hacky because I don't recall how to join the async blocks in ObjC at the end, so I just used an 0.4 second timer, lol.

#import "ReactNativeShareExtension.h"
#import "React/RCTRootView.h"
#import <MobileCoreServices/MobileCoreServices.h>

#define URL_IDENTIFIER @"public.url"
#define IMAGE_IDENTIFIER @"public.image"
#define TEXT_IDENTIFIER (NSString *)kUTTypePlainText

NSExtensionContext* extensionContext;

@implementation ReactNativeShareExtension {
    NSTimer *autoTimer;
    NSString* type;
    NSString* value;
}

- (UIView*) shareView {
    return nil;
}

RCT_EXPORT_MODULE();

- (void)viewDidLoad {
    [super viewDidLoad];

    //object variable for extension doesn't work for react-native. It must be assign to gloabl
    //variable extensionContext. in this way, both exported method can touch extensionContext
    extensionContext = self.extensionContext;

    UIView *rootView = [self shareView];
    if (rootView.backgroundColor == nil) {
        rootView.backgroundColor = [[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:0.1];
    }

    self.view = rootView;
}


RCT_EXPORT_METHOD(close) {
    [extensionContext completeRequestReturningItems:nil
                                  completionHandler:nil];
}



RCT_EXPORT_METHOD(openURL:(NSString *)url) {
  UIApplication *application = [UIApplication sharedApplication];
  NSURL *urlToOpen = [NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
  [application openURL:urlToOpen options:@{} completionHandler: nil];
}



RCT_REMAP_METHOD(data,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    [self extractDataFromContext: extensionContext withCallback:^(NSDictionary* dict, NSException* err) {
        NSLog(@"react-native-share-extension dict: %@", dict);
        if(err) {
            reject(@"error", err.description, nil);
        } else {
            resolve(dict);
        }
    }];
}

- (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(NSDictionary* dict, NSException *exception)) callback {
    @try {
        NSExtensionItem *item = [context.inputItems firstObject];
        NSArray *attachments = item.attachments;

        __block NSItemProvider *urlProvider = nil;
        __block NSItemProvider *imageProvider = nil;
        __block NSItemProvider *textProvider = nil;

        [attachments enumerateObjectsUsingBlock:^(NSItemProvider *provider, NSUInteger idx, BOOL *stop) {
            if([provider hasItemConformingToTypeIdentifier:URL_IDENTIFIER]) {
                urlProvider = provider;
            } else if ([provider hasItemConformingToTypeIdentifier:TEXT_IDENTIFIER]){
                textProvider = provider;
            } else if ([provider hasItemConformingToTypeIdentifier:IMAGE_IDENTIFIER]){
                imageProvider = provider;
            }
        }];

        __block NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
        __block BOOL ok = false;

        if(urlProvider) {
            [urlProvider loadItemForTypeIdentifier:URL_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
                NSURL *url = (NSURL *)item;

                [dict setObject:[url absoluteString] forKey:@"url"];
            }];
            ok = true;
        }
        if (imageProvider) {
            [imageProvider loadItemForTypeIdentifier:IMAGE_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
                NSURL *url = (NSURL *)item;

                [dict setObject:[url absoluteString] forKey:[[[url absoluteString] pathExtension] lowercaseString]];
            }];
            ok = true;
        }
        if (textProvider) {
            [textProvider loadItemForTypeIdentifier:TEXT_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
                NSString *text = (NSString *)item;

                [dict setObject:text forKey:@"text"];
            }];
            ok = true;
        }

        if (!callback) return;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.4 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            if (ok) {
                if (callback) {
                    callback(dict, nil);
                }
            } else {
                if (callback) {
                    callback(nil, [NSException exceptionWithName:@"Error" reason:@"couldn't find provider" userInfo:nil]);
                }
            }
        });
    }
    @catch (NSException *exception) {
        if(callback) {
            callback(nil, exception);
        }
    }
}

@end

Maybe someone more ObjC-experienced can make a PR.