Building iOS App Extensions With React Native

iOS App Extensions are a great way to let your users take advantage of your app’s functionality while not in your app. For example, you could add a Share Extension to allow users to quickly share a link from Safari via your social media app. Or, you could add an Action Extension to allow users of your note-taking app to annotate an article she was reading in another app.

Configure the Action Extension to be able to run the JavaScript runtime.

Change the Action Extension to render our React Native view.

Show a custom React Native view for our Action Extension.

Hook up a “Done” button so users can return the original app.

Create an Action Extension with Ignite

Prior to this point, I’ll assume you’re working with an existing application. If not, install Ignite and generate a new app with ignite new ActionExtensionExample.

Now, we’ll switch over to XCode to add an Action Extension to our app. Begin by adding a new target to the XCode project:

We’ll choose “Action Extension” to create our extension:

When configuring the extension, be sure to choose “Objective-C” for “Language”, and “Presents User Interface” for the “Action Type”:

After clicking “Finish”, you’ll have a basic Action Extension as part of your app. You can check out the example ActionViewController.m for an example of what the Action Extension can do. Don’t get too attached to that code, though, since we’ll be re-writing most of it shortly.

Configure the Action Extension for React Native

The goal of this part of the process is to make our Action Extension configuration look like the configuration for our React Native target’s configuration.

We’re using React Native version 0.38.0, so if your app’s configuration is different than what you’re seeing in this post, favor what your app’s configuration is.

To begin, link the necessary libraries with the Action Extension target. In the “Build Settings” tab of the Target configuration, add the following libraries:

Note that some extra libraries are required for an Ignite application.

Next, we need to add some linker flags to allow the extension to compile correctly. Add -ObjC and -lc++ to the “Other Linker Flags” setting in the “Build Settings” tab of the target configuration:

In order to allow the JavaScript bundle to be loaded, we need to add an exception in “App Transport Security Settings” to allow localhost to be loaded. In the Info.plist of the Action Extension add the following exception:

Now’s a good time to pause and make sure that everything’s set up properly. Re-build and install the app in the simulator with react-native run-ios. The extension will be built and installed along with the app and you shouldn’t have any errors.

To make sure that the extension is installed, open Safari in the iPhone Simulator and press the “Share” button at the bottom of the screen. You won’t see your extension yet, since it first needs to be enabled. Scroll over all the way to the right in the action row until you see the “More” link:

You should see your new action extension at the bottom of the list:

Toggle the enabled switch and drag it up to the beginning for easier testing:

Now when you open the share link in Safari you should see your action extension!

Clicking on the action extension will open the default, example view controller, so let’s make that our own instead.

Show React Native View in Action Extension

As mentioned previously, the generated Action Extension included a default view controller in ActionViewController.m. That view controller implements viewDidLoad to implement the example functionality. We’re going to replace that method with a loadView method that creates an RCTRootView with our app’s JavaScript bundle (see also this commit in our example app):

Note that most of this code was gently lifted straight from the AppDelegate.m file in the React Native app (with nil replacing launchOptions, since the view does not accept any options).

Show Custom React Native View from Action Extension

If you were to try out your Action Extension now (after rebuilding first), you’d see the whole React Native app loaded after clicking on the action. Since the goal of an Action Extension is to provide some single piece of functionality, we need to update our app to allow showing a special view in our Action Extension.

We’ll accomplish this by passing in an initial property to designate when our app is started inside an Action Extension and render a different component based on that property. (See also this commit in our example app.)

First, update the loadView method in ActionViewController.m to pass in a dictionary with isActionExtension set to true for the initialProperties argument of initWithBundleURL:

We can update the root component of our application to render this new screen when the desired prop is true. In an Ignite application, this component is in App/Containers/App.js. Update the component to check the isActionExtension prop:

Now, when we rebuild the app and open our Action Extension, we should see our new view!

Unfortunately, though, there’s no way to exit the Action Extension without quitting Safari. Let’s fix that.

Add a “Done” button to dismiss the extension

An Action Extension can only be closed by calling completeRequestReturningItems on success or cancelRequestWithError on error on the extensionContext. Since we can’t do that directly from our JavaScript code, we need to add a new Native Module that will handle this for us.

The Native Module is slightly complicated by the fact that Action Extensions do not have access to the application’s context so we have to keep track of the Action Extension’s view controller ourselves. We’ll update the ActionViewController.h to export a pointer to the view controller and set that pointer when our view is loaded. (See this commit in our example repo.)