alinz / react-native-share-extension

react-native as an engine to drive share extension
MIT License
766 stars 401 forks source link

Working in React Native 0.62.2 (iOS) #209

Open ortonomy opened 4 years ago

ortonomy commented 4 years ago

Really appreciate the work that @jvandenaardweg went to over in issue #182 to try and help everybody out, but I thought I'd open this here to share a solution that doesn't need to modify build settings or fork any repos. I would suggest just writing your own share extension and using some skeleton code from this issue. You can still follow most of the install steps, but without importing this lib, and a few additional steps.

  1. Create your iOS share extension (in current install steps)

In X-Code, having opened up your project

File > New > Target -> ShareExtension

  1. In your podfile, set your target to use all of the react-native libs
# right below use_native_modules!

# for share extension
  target 'name_of_your_share' do
    use_native_modules!
    inherit! :complete
  end
  1. Replace the default code with an extension of UIViewController and implement viewDidLoad to load react native

MyShareExtension.h

#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>

@interface MyShareExtension : UIViewController<RCTBridgeModule>
@end

MyShareExtension.m

#import "MyShareExtension.h"
#import <React/RCTRootView.h>
#import <React/RCTBundleURLProvider.h>

NSExtensionContext* extensionContext;

@implementation MyShareExtension {
}

RCT_EXPORT_MODULE();

- (void) viewDidLoad {
  [super viewDidLoad];

  extensionContext = self.extensionContext; // global for later call to async promise

  // set up react native instance
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                               moduleName: @"your_target_react_native_app" // AppRegistry.registerComponent("your_target_react_native_app", App)
                                               initialProperties: nil
                                                   launchOptions: nil];

  UIViewController *rootViewController = [UIViewController alloc];
  rootViewController.view = rootView;
  [self addChildViewController: rootViewController];

  rootViewController.view.frame = self.view.bounds;
  rootViewController.view.translatesAutoresizingMaskIntoConstraints = false;
  [[self view] addSubview:rootViewController.view];
  NSArray* constraints = [NSArray arrayWithObjects:
                          [rootViewController.view.leftAnchor constraintEqualToAnchor: self.view.leftAnchor],
                          [rootViewController.view.rightAnchor constraintEqualToAnchor: self.view.rightAnchor],
                          [rootViewController.view.topAnchor constraintEqualToAnchor: self.view.topAnchor],
                          [rootViewController.view.bottomAnchor constraintEqualToAnchor: self.view.bottomAnchor], nil
                        ];
  [NSLayoutConstraint activateConstraints:constraints];

  [self didMoveToParentViewController: self];

}
  1. Pretty much copy the content from this lib and expose methods to react native to fetch the parameters capturing from the share extension
// top of file
#import <MobileCoreServices/MobileCoreServices.h>

#define URL_IDENTIFIER (NSString *)kUTTypeURL
#define IMAGE_IDENTIFIER (NSString *)kUTTypeImage

// ... more code

RCT_REMAP_METHOD(getParameters,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    [self extractData: extensionContext withCallback:^(NSDictionary* result, NSException* err) {
        if(err) {
            reject(@"error", err.description, nil);
        } else {
            resolve(result);
        }
    }];
}

- (void) extractData: (NSExtensionContext *)context withCallback:(void(^)(NSDictionary* result, NSException *exception))callback {
  @try {

    // get items shared
    NSExtensionItem *item = [context.inputItems firstObject];
    __block NSItemProvider *provider = item.attachments.firstObject;

    if ([provider hasItemConformingToTypeIdentifier:IMAGE_IDENTIFIER]){
      [provider loadItemForTypeIdentifier:IMAGE_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
        NSURL *url = (NSURL *)item;
        NSDictionary *result = @{@"data": [url absoluteString], @"extension": [[[url absoluteString] pathExtension] lowercaseString], @"type": @"image"};
        if(callback) {
            callback(result, nil);
        }
      }];
      return;
    }

    if([provider hasItemConformingToTypeIdentifier:URL_IDENTIFIER]) {
      [provider loadItemForTypeIdentifier:URL_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
        NSURL *url = (NSURL *)item;
        NSDictionary *result = @{@"data": [url absoluteString], @"type": @"url"};
        if(callback) {
            callback(result, nil);
        }
      }];

      return;

    }

    if(callback) {
      callback(nil, [NSException exceptionWithName:@"Error" reason:@"couldn't find provider" userInfo:nil]);
    }
  }
  @catch (NSException *exception) {
  }
}

Conclusion

You still need to follow the install steps to restrict the types that your app can receive (The plist) and ensure the transport settings let you connect to the react native bundler server, but these were the most critical steps to getting my share extension working.

ankit2049 commented 4 years ago

@ortonomy Thanks a lot. It was working perfectly for me until I tried to add expo-notifications in my project. As soon as I add import * as Notifications from 'expo-notifications'; in the main app, I see a blank screen in the share extension. Did you ever encounter something similar to this by any chance? Here's my pod file:

platform :ios, '11.0'
require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

def add_flipper_pods!(versions = {})
  versions['Flipper'] ||= '~> 0.33.1'
  versions['DoubleConversion'] ||= '1.1.7'
  versions['Flipper-Folly'] ||= '~> 2.1'
  versions['Flipper-Glog'] ||= '0.3.6'
  versions['Flipper-PeerTalk'] ||= '~> 0.0.4'
  versions['Flipper-RSocket'] ||= '~> 1.0'

  pod 'FlipperKit', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FlipperKitLayoutPlugin', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/SKIOSNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FlipperKitUserDefaultsPlugin', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FlipperKitReactPlugin', versions['Flipper'], :configuration => 'Debug'

  # List all transitive dependencies for FlipperKit pods
  # to avoid them being linked in Release builds
  pod 'Flipper', versions['Flipper'], :configuration => 'Debug'
  pod 'Flipper-DoubleConversion', versions['DoubleConversion'], :configuration => 'Debug'
  pod 'Flipper-Folly', versions['Flipper-Folly'], :configuration => 'Debug'
  pod 'Flipper-Glog', versions['Flipper-Glog'], :configuration => 'Debug'
  pod 'Flipper-PeerTalk', versions['Flipper-PeerTalk'], :configuration => 'Debug'
  pod 'Flipper-RSocket', versions['Flipper-RSocket'], :configuration => 'Debug'
  pod 'FlipperKit/Core', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/CppBridge', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FBCxxFollyDynamicConvert', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FBDefines', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FKPortForwarding', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FlipperKitHighlightOverlay', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FlipperKitLayoutTextSearchable', versions['Flipper'], :configuration => 'Debug'
  pod 'FlipperKit/FlipperKitNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
end

# Post Install processing for Flipper
def flipper_post_install(installer)
  installer.pods_project.targets.each do |target|
    if target.name == 'YogaKit'
      target.build_configurations.each do |config|
        config.build_settings['SWIFT_VERSION'] = '4.1'
      end
    end
  end
end

target 'myapp' do
  # Pods for myapp
  pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
  pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
  pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
  pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
  pod 'React', :path => '../node_modules/react-native/'
  pod 'React-Core', :path => '../node_modules/react-native/'
  pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
  pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
  pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
  pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
  pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
  pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
  pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
  pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
  pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
  pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
  pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
  pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'

  pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
  pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
  pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
  pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
  pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
  pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
  pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true

  pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
  pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
  pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'

  use_unimodules!
  use_native_modules!

  # Enables Flipper.
  #
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable these next few lines.
  add_flipper_pods!

  target 'MyShareEx' do
    use_native_modules!
    inherit! :complete
  end

  post_install do |installer|
    flipper_post_install(installer)
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
      end
    end
  end
end
ankit2049 commented 4 years ago

https://github.com/alinz/react-native-share-extension/issues/48#issuecomment-317587122 resolved the issue.

azesmway commented 4 years ago

@ortonomy Hey. you tried to use Share in ios 14? My code worked fine for ios 13 and below, but with the transitions to ios 14, an empty window is displayed and quickly closes. I haven't changed the code until I can understand what could be the reason

in the simulator - works great, on a real device - no

mgvictor7 commented 4 years ago

@ortonomy Hey. you tried to use Share in ios 14? My code worked fine for ios 13 and below, but with the transitions to ios 14, an empty window is displayed and quickly closes. I haven't changed the code until I can understand what could be the reason

in the simulator - works great, on a real device - no

I have the same issue. In the simulator - works, on a real device - no (IOS 13.7 and IOS 14) react-native 0.63.2

ortonomy commented 4 years ago

hi @mgvictor7 and @azesmway -- mine is working on iOS 14:

1621600747653_ pic_hd

For reference, I've upgraded to the latest React Native (0.63.2) which has an upgrade guide here which massively simplifies your pod file: https://react-native-community.github.io/upgrade-helper/?from=0.62.2&to=0.63.2

For example, after upgrade, this is what my pod file looks like:

# frozen_string_literal: true

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '10.0'

target 'KeewishReact' do
  config = use_native_modules!

  use_react_native!(path: config['reactNativePath'])

  #   # React native permissions
  permissions_path = '../node_modules/react-native-permissions/ios'
  pod 'Permission-Camera', path: "#{permissions_path}/Camera.podspec"

  # for share extension
  target 'KeewishShare' do
    inherit! :complete
  end

  target 'KeewishReactTests' do
    inherit! :complete
    # Pods for testing
  end

  # Enables Flipper.
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable these next few lines.
  use_flipper!

  post_install do |installer|
    flipper_post_install(installer)

    # https://github.com/facebook/react-native/issues/25792
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
      end
    end
  end
end

FYI: I also have NOT used this extension. I built it myself based on content of this extension:

header

//
//  ShareViewController.h
//  KeewishShare
//
//  Created by Gregory Orton on 24/07/2020.
//

#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>

@interface KeewishShareExtension : UIViewController<RCTBridgeModule>
  - (void) shareView;
@end

.m

//
//  ShareViewController.m
//  KeewishShare
//
//  Created by Gregory Orton on 24/07/2020.
//

#import <Foundation/Foundation.h>
#import "KeewishShareExtension.h"
#import <React/RCTRootView.h>
#import <React/RCTBundleURLProvider.h>
#import <MobileCoreServices/MobileCoreServices.h>

NSExtensionContext* extensionContext;

#define URL_IDENTIFIER (NSString *)kUTTypeURL
#define IMAGE_IDENTIFIER (NSString *)kUTTypeImage
//#define TEXT_IDENTIFIER (NSString *)kUTTypePlainText

@implementation KeewishShareExtension {
}

RCT_EXPORT_MODULE();

- (void) shareView {
  extensionContext = self.extensionContext; // global for later call to async promise

  // set up react native instance
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                               moduleName: @"KeewishReactShare"
                                               initialProperties: nil
                                                   launchOptions: nil];

  UIViewController *rootViewController = [UIViewController alloc];
  rootViewController.view = rootView;
  [self addChildViewController: rootViewController];

  rootViewController.view.frame = self.view.bounds;
  rootViewController.view.translatesAutoresizingMaskIntoConstraints = false;
  [[self view] addSubview:rootViewController.view];
  NSArray* constraints = [NSArray arrayWithObjects:
                          [rootViewController.view.leftAnchor constraintEqualToAnchor: self.view.leftAnchor],
                          [rootViewController.view.rightAnchor constraintEqualToAnchor: self.view.rightAnchor],
                          [rootViewController.view.topAnchor constraintEqualToAnchor: self.view.topAnchor],
                          [rootViewController.view.bottomAnchor constraintEqualToAnchor: self.view.bottomAnchor], nil
                        ];
  [NSLayoutConstraint activateConstraints:constraints];

  [self didMoveToParentViewController: self];
}

- (void) viewDidLoad {
  [super viewDidLoad];

  // object variable for extension doesn't work for react-native. It must be assigned to gloabl
  // variable extensionContext
  extensionContext = self.extensionContext;

  // generate react native bundle and views
  [self shareView];

}

RCT_REMAP_METHOD(getParameters,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    [self extractData: extensionContext withCallback:^(NSDictionary* result, NSException* err) {
        if(err) {
            reject(@"error", err.description, nil);
        } else {
            resolve(result);
        }
    }];
}

- (void) extractData: (NSExtensionContext *)context withCallback:(void(^)(NSDictionary* result, NSException *exception))callback {
  @try {

    // get items shared
    NSExtensionItem *item = [context.inputItems firstObject];
    __block NSItemProvider *provider = item.attachments.firstObject;

    if ([provider hasItemConformingToTypeIdentifier:IMAGE_IDENTIFIER]){
      [provider loadItemForTypeIdentifier:IMAGE_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
        NSURL *url = (NSURL *)item;
        NSDictionary *result = @{@"data": [url absoluteString], @"extension": [[[url absoluteString] pathExtension] lowercaseString], @"type": @"image"};
        if(callback) {
            callback(result, nil);
        }
      }];
      return;
    }

    if([provider hasItemConformingToTypeIdentifier:URL_IDENTIFIER]) {
      [provider loadItemForTypeIdentifier:URL_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
        NSURL *url = (NSURL *)item;
        NSDictionary *result = @{@"data": [url absoluteString], @"type": @"url"};
        if(callback) {
            callback(result, nil);
        }
      }];

      return;

    }

//    if ([provider hasItemConformingToTypeIdentifier:TEXT_IDENTIFIER]){
//      NSString *text = (NSString *)provider;
//      return;
//    }

    if(callback) {
      callback(nil, [NSException exceptionWithName:@"Error" reason:@"couldn't find provider" userInfo:nil]);
    }
  }
  @catch (NSException *exception) {
  }
}

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

+ (BOOL)requiresMainQueueSetup
{
  // only do this if your module initialization relies on calling UIKit!
  return YES;
}

@end
azesmway commented 4 years ago

@ortonomy Hi. If it is possible, can I see - KeewishShareExtension.h, KeewishShareExtension.m and index.js, since my transparent window pops up and everything, tried in various ways?

ortonomy commented 4 years ago

@azesmway - you just saw them. I've labelled them.

azesmway commented 4 years ago

@ortonomy I apologize for my english, I am writing through google translator. I tried to repeat everything as you wrote - an empty window appears and nothing happens

mgvictor7 commented 4 years ago

@ortonomy thanks for everything. But still can't get it to work on real devices. When execute this code un real device jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; jsCodeLocation return null!

When execute in the simulator, jsCodeLocation return http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false

azesmway commented 4 years ago

@mgvictor7 Hi. The solution from this post helped me. Worked on a real device https://github.com/alinz/react-native-share-extension/issues/182#issuecomment-587006020

azesmway commented 4 years ago

@ortonomy But now there is a new problem. Opening the sharing window causes the entire application to start and in no way can I restrict this launch.

AppRegistry.registerComponent ('MyShare', () => MyShare)
AppRegistry.registerComponent (appName, () => App)

Calling "MyShare" compiles and runs "App"

garridio85 commented 4 years ago

Hey @ortonomy,

Thanks for the walkthrough! It works almost perfectly. I am having an issue retrieving the data on the native side, it is return an error of null.

Do you know what is missing? I can see the modal and everything just no data.

ortonomy commented 4 years ago

You need to access your module from import { NativeModules } from 'react-native'

NativeModules.your_module_name.data().then(d => ....)

technoplato commented 4 years ago

@ortonomy appreciate you putting together this solution. Could you show your JavaScript usage of your functioning product? I’m going to make a tutorial following your instructions to hopefully help some people (including myself) out with this.

Meandmybadself commented 3 years ago

Thank you for your help w/ this, @ortonomy .

I'm developing a basic sharing starter app & am running into the following compilation-time error:

Apple Mach-O Linker Error
"_OBJC_CLASS_$_RCTRootView", referenced from:
"_OBJC_CLASS_$_RCTBundleURLProvider", referenced from:
"_RCTRegisterModule", referenced from:
Linker command failed with exit code 1 (use -v to see invocation)

It appears that maybe the the share extension doesn't know where to look for React Native dependencies?

Have tried:

Any pointers on what I'm doing wrong? Again, thank you for all your help.

react-native-share-starter

Podfile

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '11.0'

target 'episogood' do
  config = use_native_modules!

  use_react_native!(:path => config["reactNativePath"])

  target 'EpisogoodShare' do
    inherit! :complete
  end

  # Enables Flipper.
  #
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable these next few lines.
  use_flipper!
  post_install do |installer|
    flipper_post_install(installer)
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
      end
    end
  end
end

ShareviewController.h

#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>

@interface ShareViewController : UIViewController<RCTBridgeModule>
@end

ShareviewController.m

#import "ShareViewController.h"
#import <React/RCTRootView.h>
#import <React/RCTBundleURLProvider.h>
#import <MobileCoreServices/MobileCoreServices.h>

#define URL_IDENTIFIER (NSString *)kUTTypeURL

NSExtensionContext* extensionContext;

@implementation ShareViewController {
}

RCT_EXPORT_MODULE();

- (void) viewDidLoad {
  [super viewDidLoad];

  extensionContext = self.extensionContext; // global for later call to async promise

  // set up react native instance
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                               moduleName: @"Share"
                                               initialProperties: nil
                                                   launchOptions: nil];

  UIViewController *rootViewController = [UIViewController alloc];
  rootViewController.view = rootView;
  [self addChildViewController: rootViewController];

  rootViewController.view.frame = self.view.bounds;
  rootViewController.view.translatesAutoresizingMaskIntoConstraints = false;
  [[self view] addSubview:rootViewController.view];
  NSArray* constraints = [NSArray arrayWithObjects:
                          [rootViewController.view.leftAnchor constraintEqualToAnchor: self.view.leftAnchor],
                          [rootViewController.view.rightAnchor constraintEqualToAnchor: self.view.rightAnchor],
                          [rootViewController.view.topAnchor constraintEqualToAnchor: self.view.topAnchor],
                          [rootViewController.view.bottomAnchor constraintEqualToAnchor: self.view.bottomAnchor], nil
                        ];
  [NSLayoutConstraint activateConstraints:constraints];

  [self didMoveToParentViewController: self];

}

RCT_REMAP_METHOD(getParameters,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    [self extractData: extensionContext withCallback:^(NSDictionary* result, NSException* err) {
        if(err) {
            reject(@"error", err.description, nil);
        } else {
            resolve(result);
        }
    }];
}

- (void) extractData: (NSExtensionContext *)context withCallback:(void(^)(NSDictionary* result, NSException *exception))callback {
  @try {

    // get items shared
    NSExtensionItem *item = [context.inputItems firstObject];
    __block NSItemProvider *provider = item.attachments.firstObject;

    if([provider hasItemConformingToTypeIdentifier:URL_IDENTIFIER]) {
      [provider loadItemForTypeIdentifier:URL_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
        NSURL *url = (NSURL *)item;
        NSDictionary *result = @{@"data": [url absoluteString], @"type": @"url"};
        if(callback) {
            callback(result, nil);
        }
      }];

      return;

    }

    if(callback) {
      callback(nil, [NSException exceptionWithName:@"Error" reason:@"couldn't find provider" userInfo:nil]);
    }
  }
  @catch (NSException *exception) {
  }
}

@end
ReneMarquez commented 3 years ago

You need to access your module from import { NativeModules } from 'react-native'

NativeModules.your_module_name.data().then(d => ....)

Hello, I used your guide to have this library working, I'm able to get the data on my "Sharing" extension

`import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import Share from './Share';

AppRegistry.registerComponent('AlertaShare', () => Share) <---- Can access data here using NativeModules
AppRegistry.registerComponent(appName, () => App);`

but I'm stuck trying to move that data to my main react native app, wondering if you have any pointers, tried using Linking but it for whatever reason it does not work

ortonomy commented 3 years ago

@ReneMarquez -- use redux/mobx and redux-persist to share state? In iOS the share applet and the react native app are essentially two different apps.

I used to AppGroups to make sure the state is shared between the two. ( I needed to store photos for later upload to a server)

MarkusPint commented 2 years ago

Thank you for your help w/ this, @ortonomy .

I'm developing a basic sharing starter app & am running into the following compilation-time error:

Apple Mach-O Linker Error
"_OBJC_CLASS_$_RCTRootView", referenced from:
"_OBJC_CLASS_$_RCTBundleURLProvider", referenced from:
"_RCTRegisterModule", referenced from:
Linker command failed with exit code 1 (use -v to see invocation)

It appears that maybe the the share extension doesn't know where to look for React Native dependencies?

Have tried:

  • Adding $(inherited) in header & library search paths (no real change in functionality)
  • Adding the React Libraries to the ShareExtension dependencies (then, it can't find the newly-added dependencies)
  • Using Legacy Mode to compile (just shows a different version of the error above)

Any pointers on what I'm doing wrong? Again, thank you for all your help.

react-native-share-starter

Podfile

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '11.0'

target 'episogood' do
  config = use_native_modules!

  use_react_native!(:path => config["reactNativePath"])

  target 'EpisogoodShare' do
    inherit! :complete
  end

  # Enables Flipper.
  #
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable these next few lines.
  use_flipper!
  post_install do |installer|
    flipper_post_install(installer)
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
      end
    end
  end
end

ShareviewController.h

#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>

@interface ShareViewController : UIViewController<RCTBridgeModule>
@end

ShareviewController.m

#import "ShareViewController.h"
#import <React/RCTRootView.h>
#import <React/RCTBundleURLProvider.h>
#import <MobileCoreServices/MobileCoreServices.h>

#define URL_IDENTIFIER (NSString *)kUTTypeURL

NSExtensionContext* extensionContext;

@implementation ShareViewController {
}

RCT_EXPORT_MODULE();

- (void) viewDidLoad {
  [super viewDidLoad];

  extensionContext = self.extensionContext; // global for later call to async promise

  // set up react native instance
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                               moduleName: @"Share"
                                               initialProperties: nil
                                                   launchOptions: nil];

  UIViewController *rootViewController = [UIViewController alloc];
  rootViewController.view = rootView;
  [self addChildViewController: rootViewController];

  rootViewController.view.frame = self.view.bounds;
  rootViewController.view.translatesAutoresizingMaskIntoConstraints = false;
  [[self view] addSubview:rootViewController.view];
  NSArray* constraints = [NSArray arrayWithObjects:
                          [rootViewController.view.leftAnchor constraintEqualToAnchor: self.view.leftAnchor],
                          [rootViewController.view.rightAnchor constraintEqualToAnchor: self.view.rightAnchor],
                          [rootViewController.view.topAnchor constraintEqualToAnchor: self.view.topAnchor],
                          [rootViewController.view.bottomAnchor constraintEqualToAnchor: self.view.bottomAnchor], nil
                        ];
  [NSLayoutConstraint activateConstraints:constraints];

  [self didMoveToParentViewController: self];

}

RCT_REMAP_METHOD(getParameters,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    [self extractData: extensionContext withCallback:^(NSDictionary* result, NSException* err) {
        if(err) {
            reject(@"error", err.description, nil);
        } else {
            resolve(result);
        }
    }];
}

- (void) extractData: (NSExtensionContext *)context withCallback:(void(^)(NSDictionary* result, NSException *exception))callback {
  @try {

    // get items shared
    NSExtensionItem *item = [context.inputItems firstObject];
    __block NSItemProvider *provider = item.attachments.firstObject;

    if([provider hasItemConformingToTypeIdentifier:URL_IDENTIFIER]) {
      [provider loadItemForTypeIdentifier:URL_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
        NSURL *url = (NSURL *)item;
        NSDictionary *result = @{@"data": [url absoluteString], @"type": @"url"};
        if(callback) {
            callback(result, nil);
        }
      }];

      return;

    }

    if(callback) {
      callback(nil, [NSException exceptionWithName:@"Error" reason:@"couldn't find provider" userInfo:nil]);
    }
  }
  @catch (NSException *exception) {
  }
}

@end

Did you ever solve this?