Code Monkey home page Code Monkey logo

ios-share-extension's Introduction

Expo Share Extension

npm License Downloads GitHub stars

Create an iOS share extension with a custom view (similar to e.g. Pinterest). Supports Apple Sign-In, React Native Firebase (including shared auth session via access groups), custom background, custom height, and custom fonts.

Note: The extension currently only works for Safari's share menu, where a url prop is passed to the extension's root component as an initial prop. Contributions to support more NSExtensionActivationRules are welcome!

expo-share-extension-720.mov

Installation

Install the package

npx expo install expo-share-extension

Update app.json/app.config.js

"expo": {
  ...
  "plugins": ["expo-share-extension"],
  ...
}

Update package.json

{
  ...
  "main": "index.js",
  ...
}

Create an index.js in the root of your project

import { registerRootComponent } from "expo";

import App from "./App";

registerRootComponent(App);

or if you're using expo-router:

import "expo-router/entry";

Create an index.share.js in the root of your project

import { AppRegistry } from "react-native";

// could be any component you want to use as the root component of your share extension's bundle
import ShareExtension from "./ShareExtension";

// IMPORTANT: the first argument to registerComponent, must be "shareExtension"
AppRegistry.registerComponent("shareExtension", () => ShareExtension);

Update metro.config.js so that it resolves index.share.js as the entry point for the share extension

// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require("expo/metro-config");

/**
 * Add support for share.js as a recognized extension to the Metro config.
 * This allows creating an index.share.js entry point for our iOS share extension
 *
 * @param {import('expo/metro-config').MetroConfig} config
 * @returns {import('expo/metro-config').MetroConfig}
 */
function withShareExtension(config) {
  config.transformer.getTransformOptions = () => ({
    resolver: {
      sourceExts: [...config.resolver.sourceExts, "share.js"], // Add 'share.js' as a recognized extension
    },
  });
  return config;
}

module.exports = withShareExtension(getDefaultConfig(__dirname), {
  // [Web-only]: Enables CSS support in Metro.
  isCSSEnabled: true,
});

Need a way to close the share extension? Use the close method from expo-share-extension:

import { close } from "expo-share-extension"
import { Button, Text, View } from "react-native";

// if ShareExtension is your root component, url is available as an initial prop
export default function ShareExtension({ url }: { url: string }) {
  return (
    <View style={{ flex: 1 }}>
      <Text>{url}</Text>
      <Button title="Close" onPress={close} />
    </View>
  );
}

Options

Exlude Expo Modules

Exclude unneeded expo modules to reduce the share extension's bundle size by adding the following to your app.json/app.config.(j|t)s:

[
  "expo-share-extension",
    {
      "excludedPackages": [
        "expo-dev-client",
        "expo-splash-screen",
        "expo-updates",
        "expo-font",
      ],
    },
],

React Native Firebase

Using React Native Firebase? Given that share extensions are separate iOS targets, they have their own bundle IDs, so we need to create a dedicated GoogleService-Info.plist in the Firebase console, just for the share extension target. The bundle ID of your share extension is your existing bundle ID with .ShareExtension as the suffix, e.g. com.example.app.ShareExtension.

[
  "expo-share-extension",
    {
      "googleServicesFile": "./path-to-your-separate/GoogleService-Info.plist",
    },
],

You can share a firebase auth session between your main app and the share extension by using the useUserAccessGroup hook. The value for userAccessGroup is your main app's bundle ID with the group. prefix, e.g. group.com.example.app. For a full example, check this.

Custom Background Color

Want to customize the share extension's background color? Add the following to your app.json/app.config.(j|t)s:

[
  "expo-share-extension",
    {
      "backgroundColor": {
        "red": 255,
        "green": 255,
        "blue": 255,
        "alpha": 0.8 // if 0, the background will be transparent
      },
    },
],

Custom Height

Want to customize the share extension's height? Do this in your app.json/app.config.(j|t)s:

[
  "expo-share-extension",
    {
      "height": 500
    },
],

Custom Fonts

This plugin automatically adds custom fonts to the share extension target if they are embedded in the native project via the expo-font config plugin.

It currently does not support custom fonts that are loaded at runtime, due to an NSURLSesssion error. To fix this, Expo would need to support defining a sharedContainerIdentifier for NSURLSessionConfiguration instances, where the value would be set to the main app's and share extension's app group identifier (e.g. group.com.example.app).

Preprocessing JavaScript

As explained in Accessing a Webpage, we can use a JavaScript file to preprocess the webpage before the share extension is activated. This is useful if you want to extract the title and URL of the webpage, for example. To use this feature, add the following to your app.json/app.config.(j|t)s:

[
  "expo-share-extension",
    {
      "preprocessingFile": "./preprocessing.js"
    },
],

The preprocessingFile option adds NSExtensionActivationSupportsWebPageWithMaxCount: 1 as an NSExtensionActivationRule. Your preprocessing file must adhere to some rules:

  1. You must create a class with a run method, which receives an object with a completionFunction method as its argument. This completionFunction method must be invoked at the end of your run method. The argument you pass to it, is what you will receive as the preprocessingResults object as part of initial props.
class ShareExtensionPreprocessor {
  run(args) {
    args.completionFunction({
      title: document.title,
    });
  }
}
  1. Your file must create an instance of a class using var, so that it is globally accessible.
var ExtensionPreprocessingJS = new ShareExtensionPreprocessor();

For a full example, check this.

WARNING: Using this option enbales NSExtensionActivationSupportsWebPageWithMaxCount: 1 and this is mutually exclusive with NSExtensionActivationSupportsWebURLWithMaxCount: 1, which expo-share-extension enables by default. This means that once you set the preprocessingFile option, you will no longer receive url as part of initial props. However, you can still get the URL via preprocessingResults by using window.location.href in your preprocessing file:

class ShareExtensionPreprocessor {
  run(args) {
    args.completionFunction({
      url: window.location.href,
      title: document.title,
    });
  }
}

Development

If you want to contribute to this project, you can use the example app to test your changes. Run the following commands to get started:

  1. Start the expo module build in watch mode: npm run build
  2. Start the config plugin build in watch mode: npm run build plugin
  3. cd /example and generate the iOS project: npm run prebuild
  4. Run the app from the /example folder: npm run ios

Troubleshooting

Clear XCode Cache

  1. navigate to ~/Library/Developer/Xcode/DerivedData/
  2. rm -rf folders that are prefixed with your project name

Clear CocoaPods Cache

  1. pod cache clean --all
  2. pod deintegrate

Attach Debugger to Share Extension Process:

  1. In XCode in the top menu, navigate to Debug > Attach to Process.
  2. In the submenu, you should see a list of running processes. Find your share extension's name in this list. If you don't see it, you can try typing its name into the search box at the bottom.
  3. Once you've located your share extension's process, click on it to attach the debugger to that process.
  4. With the debugger attached, you can also set breakpoints within your share extension's code. If these breakpoints are hit, Xcode will pause execution and allow you to inspect variables and step through your code, just like you would with your main app.

Check Device Logs

  1. Open the Console app from the Applications/Utilities folder
  2. Select your device from the Devices list
  3. Filter the log messages by process name matching your share extension target name

Check Crash Logs

  1. On your Mac, open Finder.
  2. Select Go > Go to Folder from the menu bar or press Shift + Cmd + G.
  3. Enter ~/Library/Logs/DiagnosticReports/ and click Go.
  4. Look for any recent crash logs related to your share extension. These logs should have a .crash or .ips extension.

Credits

This project would not be possible without existing work in the react native ecosystem. I'd like to give credit to the following projects and their authors:

ios-share-extension's People

Contributors

maxast avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.