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

Working in React Native 0.62.2 (iOS) #209

Open
ortonomy opened this issue Aug 5, 2020 · 18 comments
Open

Working in React Native 0.62.2 (iOS) #209

ortonomy opened this issue Aug 5, 2020 · 18 comments

Comments

@ortonomy
Copy link

ortonomy commented Aug 5, 2020

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.

@ortonomy ortonomy changed the title Working in React Native 0.6.2 (iOS) Working in React Native 0.62.2 (iOS) Aug 5, 2020
@ankit2049
Copy link

ankit2049 commented Sep 3, 2020

@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
Copy link

#48 (comment) resolved the issue.

@azesmway
Copy link

azesmway commented Sep 21, 2020

@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
Copy link

mgvictor7 commented Sep 21, 2020

@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
Copy link
Author

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
Copy link

@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
Copy link
Author

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

@azesmway
Copy link

@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
Copy link

@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
Copy link

@mgvictor7 Hi. The solution from this post helped me. Worked on a real device
#182 (comment)

@azesmway
Copy link

@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
Copy link

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
Copy link
Author

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

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

@technoplato
Copy link

@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
Copy link

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

@ReneMarquez
Copy link

ReneMarquez commented Mar 31, 2021

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
Copy link
Author

@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
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants