Note: Updated for Swift 4.
For a hack day at work I decided to try my hand at creating a share extension. I discovered that there wasn’t a fully-featured tutorial out there, and as a result I had to piece together information (much of it attributed, surprisingly, to the Apple documentation).
To save a web page URL as rich content into our app, and allow for configuration of a few options. Initially I thought an extension action was what was needed — but it seems that is better suited for modifying content (such as removing red-eye on a photo with one tap). It seems others, Apple included, are using the share extension to create items instead.
This is what we’ll have built by the end:
The completed extension
As you can see I’ve customized the design a little, and added what’s referred to as a “configuration item” which are cells at the bottom of the sheet; commonly used to select from accounts or other options. It’s used here to select from the user’s existing “Decks” to save to. What you can’t see is it’s also running some JavaScript code to fetch the current URL of the browser, which we send to the server later. We’ll cover all of this ahead!
Either create a new project, or open an existing one you’d like to add the extension to. Then hit File > New > Target. Under the iOS tab select Share Extension and press Next. Give it a name; I opted for the simple “ShareExtension”.
It will ask if you’d like to activate the scheme. This means when you run the application it will allow you to open straight into Safari or similar to test the extension, as opposed to your main app. We want this — tap Activate.
Test what you have so far by running the project Cmd + R — as mentioned it gives you options of which app to test from, select Safari and tap Run.
The dialog presented when running an extension.
When Safari opens, tap the share icon and your extension name with the placeholder icon should appear in the list. If not, tap More and enable it. Tapping on the icon will present the SLComposeServiceViewController’s default implementation, which is pretty neat right out of the box.
The share sheet automatically retrieves the page title and sets it as the body text, but generally you’ll want more than that — whether it be the URL or an image on the page. For this you can have Safari run a JavaSript file to parse the document and retrieve whatever we need.
First up, right click on your extensions folder in Xcode and select New File. You’ll find Empty inside of the Other tab, hit Next and name the file “GetURL.js”.
You’ll need to edit your extensions Info.plist file and in “NSExtension” there’s another dictionary called “NSExtensionAttributes” — add a key “NSExtensionJavaScriptPreprocessingFile” with the value of our file just created, “GetURL” (note the lack of extension).
Also add a dictionary row “NSExtensionActivationRule” which should contain the key “NSExtensionActivationSupportsWebURLWithMaxCount” with the number 1:
How your extension’s Info.plist should look when viewed as a property list
NSExtensionJavaScriptPreprocessingFile: this lets our app know of a JavaScript file that should be run on execution of the extension.
NSExtensionActivationRule: use this dictionary to add Action Extension Keys which help the system determine what types of content to activate your extension for.
Paste the following into “GetURL.js”:
Coupled with the keys we set above, iOS now will look for an object named “ExtensionPreprocessingJS” to perform at runtime. This is explained in more detail in Apple’s documentation.
In order to retrieve the data scraped with the javascript file above, you’ll need to run this block of code within the “ShareViewController.swift” viewDidLoad function:
You’ll also need this import for kUTTypePropertyList:
import MobileCoreServices
Running the extension now should successfully print the URL to the console!
Side note: add the following snippet if you’d like to customize the navigation bar as I have (replacing with your own image or just a title).
The boilerplate that came with creating your share extension will have included a method to optionally supply configuration items (the cells in the bottom of the share sheet)
override func configurationItems() -> [AnyObject]! {
Insert the below snippet, which returns an array of “SLComposeSheetConfigurationItem”:
Run your app again and you’ll see a cell at the bottom now.
Create a basic model called “Deck.swift”, which is used ahead to pass data between view controllers.
In the gif at the beginning, you’ll have noticed when tapping on the configuration cell it pushes to a new view with a tableview. Create a new file that inherits from “UIViewController” named “ShareSelectViewController.swift”. Create a tableview property and add it to the view.
I’ve also set the title and title color for this view, and prefer to keep constants like cell identifiers in a struct :)
Create a property to store the Deck’s passed in from the original view controller:
var userDecks = [Deck]()
And conform to the required protocols for the tableview:
Back in the“ShareViewController.swift”, create a property to store some dummy data and populate it within viewDidLoad:
Lastly, inside the tapHandler for the configuration item we created, create an instance of the ShareSelectViewController and have it push on tap, passing the decks in as well:
Run the app. Tapping the configuration cell should push to the tableview with the dummy data.
However you’ll notice the configuration cell is still hardcoded to “Deck Title”. To have this update, create a property to store the deck selected and select the first by default:
And update the configuration cell inside configurationItems() to use it:
deck.value = selectedDeck?.title
Run the app again and you’ll see it has updated.
You’ll need a way to dismiss back to the ShareViewController when a new Deck is tapped, and have it update the UI. A protocol will suffice. Add the following to your ShareSelectViewController:
Lastly, conform the ShareViewController to the protocol just made and ensure the delegate for the ShareSelectViewController is set to self:
The selected Deck is updated, and the configuration items are reloaded to update the value. Then we pop to go back to the main screen.
Run the app and now you should be able to change decks which will update to show the correctly selected deck :)
All that’s left is to send the collected information to your server or app to do something with, which you can do inside another boilerplate method:
override func didSelectPost()
I won’t go into detail about this, but I used an NSURLSession to post to our server. I also replaced the dummy data with real decks by creating an app group and using a shared NSUserDefaults to store and retrieve data earlier retrieved within the main iOS app.
let userDefaults = NSUserDefaults(suiteName: "some.group.name")
let decks = userDefaults?.objectForKey(“userDecks”) as? NSArray
Lastly, you’ll notice my extension is named “Vurb” in the action dialog, and has an icon. You can set an icon by following this SO post. And to change the name simply change “Display Name” in the project settings for the extension_._
That’s everything, if you found this helpful please help other’s find it by recommending.
You can view the project in entirety here.