Creating Extensions for iOS and OS X, Part 2

Session 217 WWDC 2014

Take the plunge into advanced extension concepts such as creating custom action extensions. Dive deep into extension architecture to learn how to provide a consistent experience between extensions and your app. See how to utilize extensions with Safari to bridge the gap between your websites and native apps, and discover how to make great extensions that users will love.

Good morning everybody.

Thanks for coming.

Welcome to Creating Extensions for iOS and OS X, Part Two.

I'm Damien.

I'm from Core OS.

And we apparently have a user facing feature this year.

[ Cheers and Applause ]

Glad to see everybody's excited about Extensions.

Let's dive right in.

So before we talk about what an extension is, let's talk a little bit about what an app is and why Extensions are different.

So for an app, it's still in iOS 8, even with Extensions, is the most important experience to the user.

On iOS, the app owns the entire screen.

On OS X, the cursor focus goes to the front-most app.

This is still the number one priority for our users.

And on iOS, apps are completely managed by the user via the App Store.

And OS X the user can drag them to the trash, move them around.

Apps are entirely managed by the user.

So what about Extensions?

So Extensions are important to the user.

But they're not more important than the current app.

They are there to augment the app's experience.

And rather than directly managing them, Extensions come and go with apps.

So the user isn't going to be purchasing or installing individual Extensions.

They're going to be getting those Extensions with the apps that they download from the store.

So Extensions are built separately.

They are a separate target in XCode.

So we have this new section in XCode for app extension template targets.

So when you want to create an extension, you're getting a new piece of code in your app that is actually a separate bundle and admits a separate executable.

And with that executable comes a distinct set of entitlements.

And when we have a separate executable with its distinct of entitlements on disk, that allows the operating system to know that these two things, the app and the extension, are different entities.

And with that, you're getting a different process.

So an extension is running in an isolated address space from your app and it executes completely independently.

So this means that the system, even if your app is not running, can still fire up your extension, even if your app is suspended.

That doesn't affect any of your Extensions running for the user.

And because of this, the system can optimize each experience separately.

So we can schedule them, and even if the app goes away, your extension still sticks around.

So here's an example of an extension and what the user might do to invoke your extension and its UI.

Here we just have a little network thing.

So it's a new social networking app.

And it's bundling a social sharing extension.

So we see it in the far right there with the purple icon.

And I go to this site and I want to share this with my friends on my new social network.

So the user will see an icon representing my app.

But what that means is that I'm going to be using the extension for social sharing vended by that app.

So in previous releases of iOS we've been able to do this, but the Facebook and the Twitter experiences, et cetera, have all been provided by Apple.

Now we're letting anyone who wants to provide a social sharing extension not only provide it but define their own experience.

So in this case, when the user elects to share, they're getting a window that is designed by the developer of this social networking app with whatever experience whatever experience that app developer wants to provide.

So that's a high level of how Extensions are architected on our system.

Let's talk a little bit about how we actually extend the experience of your app and project it into other parts of the system.

So as we saw yesterday in Part One, Extensions are a very focused experience.

And the operating system takes care of seamlessly merging the experience that you provide in your extension with the current app or just whatever the current system UI is.

So you guys are going to want to provide code to make the experience your own.

And that's going to almost certainly mean that you're going to want to re-use at least some of the code from your app.

So we hope you've been using the Model View Controller paradigm.

Features like Extensions are exactly why we so desperately encourage you guys to adopt this design pattern year after year, because chances are with your extension, you're going to want to share at least the data model and probably some of the View Controller layers, too.

You probably don't want to share the view of your app because your app is designed around taking up the whole screen whereas your extension will not.

But that's only one-third of your code.

You don't want to have to rewrite the entire two-thirds of it.

You want to be able to share code between the two.

And if only computer science had come up with a way to allow the same code to exist in two processes without having to duplicate those code pages.

I think we might have a solution to this.

And I think that solution's called Frameworks.

So, on iOS, in iOS 8, we're now allowing you to ship Frameworks within your app bundle.

[ Applause ]

Don't get too excited.

So this is obviously new to iOS, but you've been able to do this on OS X since the dawn of time.

But it's a brand new capability to iOS.

And what we're doing with it is we're also encrypting the frameworks that you ship in your app bundle with the same encryption that your apps are encrypted with to give you that same level of protection against piracy and well, piracy.

[Laughter] So it's critical to note that these frameworks are not general code sharing mechanisms.

So you're not going to have many apps on the system referencing the same copy of a framework.

These frameworks are here to facilitate sharing between and an app and all of its Extensions.

So the code that that framework contains can be used by the app or any of the Extensions it bundles, but nothing else on the system.

So when you use when you bundle a framework with your app, there are some implications for your minimum deployment target.

If your app links the framework, we're going to change its minimum deployment target to iOS 8 because as I said before, we're encrypting these frameworks.

And previous versions of the operating system don't know about how to decrypt them when you use them.

However, if you're just shipping your app and then there's a few Extensions you have that all link in a framework, your app's minimum deployment target doesn't change at all.

Your extension functionality obviously won't be available to previous versions of iOS, but the app will still be able to run without any problems.

So that's a very brief overview of embedded frameworks on iOS.

To learn more, I encourage you to go to the session tomorrow at 3:15 in Presidio, Building Modern Frameworks, where it'll cover API usage, how to write a good framework, how to bundle it, all that good stuff.

But for now, you've got Frameworks.

And it seems like you guys are excited about it.

So, another topic for code sharing is API availability.

If you're writing one of these frameworks, or if you're bundling one of these frameworks, what code are you going to be using between your app and your extension?

So, the vast majority of our API is going to be available for Extensions to use.

But there are going to be some exceptions.

And these exceptions are going to be marked explicitly with a new kind of unavailability macro.

And here's an example of what that looks like.

So, UIApplication or with UIApplication, the shared application method has been explicitly marked as unavailable.

And the UIKit folks have helpfully provided an error message that says: Use a view controller based solution where appropriate.

I'm in Core OS, no idea what that means.

But I'm sure you guys have an idea.

Now, if you're dropping down to Super [inaudible] API that has underscores, there's going to be a corresponding availability macro there that is evaluated the same way by the tools and will generate the same errors at build time.

So that's unavailability.

Like I said, the vast majority of cases you probably won't need to worry about it.

But if you do trip across an API use in your extension that is not allowed, you'll know as soon as possible at build time before you can even run the extension.

So that's how you share code with your extension and your app.

But at run time you're going to want to create a consistent experience, too.

And a consistent experience often means sharing the same set of resources and making sure that changes the user has made to their data in your extension are reflected in the app the next time they see it, or vice versa.

So we have a few data sharing solutions for you for Extensions.

So the first thing to understand is that an extension has a separate container from its app by default.

It's just like how two apps are segregated from each other.

They don't have access to each other's data.

But for any extension you bundle, you can opt to share specific bits of data that will help you provide that consistent experience that all of our users expect.

And you can do this with what's called a shared container.

So this is based on a new concept called an App Group.

And this provides a shared storage area for kind of just general data sharing between your app your extension.

So if you have some custom data models or custom databases that your app and your extension need to use, this is a good place to put those files and access them.

However, you have to remember that there's nothing stopping your extension and your app from running at the same time.

Your app may be in a long-running background task.

It may not have been suspended yet.

And it might be writing some data to that shared container.

And then the user might bring up your extension.

So what this means is that you have to make sure to safely coordinate all the reads and writes for those files.

You have to make sure that you only write to the file when there are no readers contending for it.

And that you're only reading when the file is in a consistent state.

Otherwise, you're going to blow up your file, or it's just going to get corrupted.

So we introduce some sort of synchronization scheme here so that if the extension writes, it holds off the app.

And if the app writes, it holds off the extension.

And they're both dealing with a consistent view of the universe.

And here are a few of those technologies.

The first one is NSFileCoordination, which kind of provides a general inner process synchronization strategy for your extension and for your extension and your app.

If you're using CoreData, you can get a lot of this for free.

There are certain CoreData backing stores that are documented as being usable for multiple processes at the same time.

And sqLite also makes some of these guarantees.

So if you're using one of those two technologies, chances are you're already set.

But if you have something more custom, you're going to want to use NSFileCoordination.

But if you're talking about more structured data that is, say, vended by Apple's APIs like NSUserDefaults, you can end up sharing much more easily.

So, an app and its extension both have different defaults domains.

But you can set up a shared domain with the initWithSuiteName API.

And you create this suite, and the API manages access to it for your app and your extension so that you don't have to worry about making sure that you're not writing to that backing store when someone else is trying to read from it.

The API coordinates all of that for you.

And this is a really easy way for, say, the user to modify some setting in the app which then the extension can pick up on its next invocation.

There's a similar story for keychains.

So the shared keychain is based on an app group.

And the app group can encompass a few apps as well as your Extensions.

So by default, as I said, the extension and the app have a different keychain.

But you can set up a shared keychain with an access group.

There's an issue right now in the first beta where this is actually team ID-based.

So if you're familiar with that where you pre-fix your bundle identifier with a team ID in order to facilitate sharing, that's how you do it in the first seed.

We're going to fix this up for App Groups by the time we ship.

So in terms of privacy, it's a slightly different story.

When I say privacy, I mean the dialog that comes up when an app is trying to access certain sensitive user data like photos or contacts.

You'll get a dialog saying: "Would you like to allow this app to have access to this piece of data?"

And the user can either decline or allow.

So when the user allows your app to have access to a certain piece of data, it's actually going to cover the entirety of your app bundle, including Extensions.

So that the user, once they have approved your app for access to your photos, they don't have to then go and approve all of your Extensions for access to the same data.

Now, there are going to be some Extensions to this or sorry some exceptions to this rule.

But in general, the rule is that approval covers the entirety of your app.

So let's go over best practices a little bit.

As we said at the beginning, the front-most app is the most important thing.

So it's very important that your exceptions be lean.

These things do not have the full run of the system like the app does.

The app's still there.

It's still using the lion's share of system resources.

The Extensions are very focused, very purpose-built.

And it's important that they get in, do their job, and get out.

And be stateless.

You might know that we kill apps aggressively or suspend them on iOS.

We're doubling down on that with Extensions.

And we're going to be more aggressive.

And because of this, there is no general multi-tasking functionality available to Extensions.

So no VoIP, no long-running background tasks, that kind of thing.

We do support a short task completion before we suspend or kill the extension.

But this is meant to give you time to flush any dirty state to disk so that the user doesn't lose their data and so that the app can pick that up.

And finally, be awesome.

Make these things seamless.

Make them useful to our users.

And as a user of your products, I can't wait to be surprised and delighted by what you guys turn out.

With that, I'd like to turn it over to Aki.

[ Applause ]

Thank you.

Thank you Damien.

Hi, I'm Aki Inoue from Cocoa Group.

Today, I'm discussing an exciting new feature, Action Extensions for iOS and OS X.

Yesterday in Part One, Ian, Matt and Guy introduced Extensions and two Extensions point along with key technologies such as remote view controller and activation rules.

Damien explained foundation of technologies for Extensions.

Action is an extension point that utilizes and integrates all these best technologies.

Let's get started.

So, Action is a screenshot [inaudible] system to services, an inter-application collaboration technology available since the first release of OS X.

Action operates with user selection inside the host application that is it extends the system framework, not just the single application or service.

Earlier in this conference, you have seen one of Action Extension's Markup operating inside many applications on Yosemite.

In fact, this extension is available to image attachments throughout the system.

Let's just dive into the details.

Just like any other Extensions, Actions are described by info.plist.

First, inside NSExtension Dictionary, it's identified by NSExtensionPoint


Next, NSExtensionPrincipalClass specifies the main controller [inaudible] for your extension.

Just like Guy and Matt described yesterday, it's typically a subclass of the ViewController option.

Now, the principal class is the main controller for your extension as well as the [inaudible] to the extension APIs.

With a new View Controller property called Extension Context, you can get NSExtention Context Object, which represent connection to the host application.

Through that, you can access an array of NSExtention items.

Each item representing a logical data unit coming back from coming from the host application.

And, you can attach [inaudible] data to extension item.

Movie, image, voice, so and so forth.

The data is contained inside an object called NSItemProvider.

Okay let's back to the Info.plist.

Inside NSExtensionAttributes Dictionary, which specifies extension point-specific attributes, you declare NSExtensionservice RoleType, which [inaudible] editor or viewer.

In this example, we are using NSExtensionservice RoleTypeEditor.

That means it's only presented to the context where the document is editable.

Next, as share Extensions you can specify activation rules with NSExtensionActivationRules.

It can be NS [inaudible] for define your string or the list of supported data types.

Now, let's look at code samples.

First, you can get extension context through the extension context property.

Next, you can get extension item through item, inputItems property, which returns the array of extension items.

Finally, you can get attached data using attachments property and it's containing the NSItemProvider objects.

Once you have NSItemProvider, you can load data from the host application.

It's lazy and secure.

Using the LoadItemForTypeIdentifier options completionHandler and specifying a UTI type of your choice.

In this case, we are using a custom data type, MyDocumentUTI.

And you specify a representation of the data you want to receive.

In this case, we are using NSData and it contains the custom data format, NSDocument UTI.

Once you have that data, you can set up your user interface according to the data coming from the host application.

Next, with Action, unlike shared Extensions, it's possible that extension time return some modified data.

You've seen Markup changing and marking up your image in place and return it to the mail document.

Your extension can perform the similar thing.

When user click a Down button or similar action, you can perform and return the modified data back to their host application.

The process is reverse of loading the data coming from the host application.

First, you want to instantiate NSItemProvider with your data and data type.

Next, you can create NSExtensionItem and attach your item provider.

Finally, you can send completeRequestReturningItems completionHandler to your extension item with an array of extension items.

This actually concludes your extension life cycle.

So typically you want to return your data and conclude your NUI operation at this point.

Unlike [inaudible] application, if your viewer type, you don't have to return any data back to the host application.

So if your viewer application just like share Extensions, you can invoke completeRequestReturningItems completionHandler with new argument.

So far, I have covered how to write Action Extensions.

Now, let me explain how you can integrate Extensions into your own applications.

On iOS UIActivityViewController drives both social and share and Action Extensions.

The [inaudible] controlled by the View Controller can invoke and manage Extensions for you.

UI API-wise there's not much difference for UIActivityViewController from the previous release.

You still instantiate the View Controller with the selected items from the user.

And View Controller takes over and present the [inaudible] and voila.

One enhancement we have for iOS 8 is the support for editor actions.

We added a new property called completionWithItemsHandler to UIViewController, or UIViewActivityViewController.

With this method, if the invoked action is editor and wants to return value you could receive the result in returnItems array.

And you can replace your document content based on the returned items.


Well, if anybody know in the audience, I just can't help talk about text every year in one way or the other.

On OS X, NSTextView plays the central role in presenting Extensions to the users.

By hovering us on top of extension by hovering extension on top of text selection or image attachments, it presents [inaudible] with manual listing old Extensions available for the context.

Now I have written a simple little extension for OS X.

And I'd like to show how to simply present your Extensions to the user.

I have a text edited document that contains both text and image.

By hovering mouse cursor on top of text selection, you can reveal the list of extension supporting the text data.

Similarly, by hovering on top of attachments, you can show the list of Extensions supporting images.

In this case, in addition to the sample extension I wrote, Party Crasher, it lists Markup in the text edit application.

So you can see the Markup is an extension that works, not just in mail but any other text view inside the system.

So, let me explain this little extension called Party Crasher.

Just like many of you, I like hanging out with people.

And anytime I see pictures like this, I feel, well, kind of left out.

[ Laughter ]

So, with this extension, I can add my picture to any images in the system instantly.

[ Laughter and Applause ]

I like this picture, so let's keep it.

And the change is saved in the document.

Also, there is another way to invoke Action Extensions on OS X.

I have modified this Text Edit Application.

By the way, Text Edit Application is a core sample application for Cocoa Application.

So you can modify any way you want and learn the neat stuff about Cocoa.

With this Text Edit Application, a version Text Edit Application, you can show toolbar.

And as you can see in the toolbar, extension can register themselves as toolbar item.

And it can be shown with any toolbar inside the system.

And the action is applied to the current selected object.

It can be text or image, so on so forth.

So, let's invoke the Party Crasher for text selection.

It appends a message: Exception [inaudible] to any text selection in the system.

Okay. As you can see, Action Extension is so powerful, you can write simple extension like this.

Or you can scale up to [inaudible] as Markup.

The sky is just the limit.

I have demonstrated with the demo, I have demonstrated Extensions can support multiple data types.

It is common that you want to support as many data types as you want.

More data types you support, it's easier for users to work with your Extensions.

Let's take a look at how you can change your behavior based on the input data coming from the host application.

In this case, first we want to check if it's a text data type coming from the host application.

We use the kUTTypeText UTI type.

And it contains many text data format with hasItemConforming ToTypeIdentifer method for itemProvider you can check if the itemProvider contains the text data.

Once you make sure, you can load the data using loadItemForTypeIdentifier options completionHandler using the data representation type of your choice.

Since itemProvider has simple data coordination facility, it can map some data to the target object data type you specify.

In this case, we are using NSAttributedString.

Once you have your data, set up your user interface and code into it.

Similarly, if your text data check fails, you can check for image data.

After checking if it's image, you can load the image and as I mentioned, itemProvider can [inaudible] data type into UIImage or NSImage for you.

And if you have image, configure your user interface for image.

For OS X, it's common that your extension's user interface doesn't cover the entire host application's window.

Just like Markup.

Typically you want to overlay your user interface on top of the original representation of the host application.

It's easy to accomplish.

We have additional property for NSItemProvider on OS X.

It's called sourceFrame.

It contains the screen coordinate for the original representation inside the host application.

If you get [inaudible] rect information, and if it's not empty, you can adjust the frame according to your user interface needs.

You might want to have some additional frame around the original representation just like Markup or Party Crasher does.

Then set up two NSViewController properties, preferredScreenOrigin and preferredContentSize.

As you've seen with Mark's presentation yesterday, the preferred content size is shared between the [inaudible] and Action Extension.

By setting up these properties, the View Controller system takes over and configures your extension user interface overlaid on top of the original representation in the host application.

Now, I'd like to bring my colleague Ian Baird who's going to be discussing really, really cool way to integrate interactive Web and actions.


[ Applause ]

Thank you Aki.

So, Aki showed you how to create a custom action.

And he showed it on OS X.

And today I'm going to show you how to build a Safari custom action or how to take your custom action and enhance it for the Web.

We think you guys are really going to love this feature.

So first, you can see our custom action in the bottom row of the activity view controller as I showed you yesterday in the Part One of this talk.

And it's called TinySketch.

It can't do everything that its brother Markup can do, but it can still pack a punch.

So first, diving in, Safari Custom Actions is rich new functionality in Safari on iOS.

It gives you access to the DOM.

And there are two types of Custom Actions.

A view controller-based custom action, the kind I'm going to show you today.

And then there's a no view action like the Bing Translate action that Craig showed you in the Keynote.

How do these work?

We use some JavaScript.

Your extension will provide JavaScript along in its resources.

And Safari is going to find this.

So, here's what this JavaScript is going to look like.

We're going to expect you to provide a pre-processing JavaScript object.

And this object is going to have two methods, a run method and a finalize method.

We're going to start by calling the run method and give you access to the DOM.

And what you're going to be expected to do in this run method is to call the action arguments completion function, passing the data you want to provide to the custom action when you're done.

And when the action has completed, when the computation inside of your custom action extension has finished, we're again going to call your JavaScript.

And this time we're going to invoke the finalize method and pass the action arguments.

And as you can see in this case, we're just calling alert and showing the message, which was provided.

One of the most important things for you to remember is this global variable: ExtensionPreprocessing JavaScript.

This is going to be an instance of your object that provides these methods to Safari.

And I think this should be burned into your mind, because if you don't provide this, we're not going to be able to invoke your JavaScript.

And nothing's going to show up inside of your action.

So, it's really easy to talk about these things, but it's far better to show it.

So I'm going to show you a demo of TinySketch.

Moving right in.

Let's build and run and see what we've got.

So what I've done here is I've built a very small test application called TinySketch which contains my TinySketch custom action.

This is a good way to get started.

You might not want to dive immediately into creating an extension for Safari because there are a lot of moving parts, and it's a lot better to build it up in pieces when you can.

As you can see, we don't have anything here yet.

And we don't actually have any data.

And our Action button is not wired up.

Let's go ahead and do that.

Going back to Xcode.

TinySketch Support is our new support framework, as Damien was talking about, embedded frameworks earlier.

We're going to go into our data model.

And because our application and our extension may want to use the same data model, we're actually going to provide the accessor to the resources here, like this.

This is going to iterate through the image URLs in my custom Extensions documents folder.

We'll add support for it there.

And then we'll go back to the app and we'll actually wire up support for it in viewDidLoad.

Get the pictures of the cute kids.

Fix up the indentation.

And we'll build and run again.

And this time you'll see some pictures of my children.

They're usually enjoying a day at the park or at school or doing something else.

Hit Reset on this.

I forgot to reset it before.

We can tap on any of these images and in they go.

You can see right there.

Tap on any of them.

It's really cool.

All right.

Tap the Custom Action button, and nothing comes up.

Well, that's because we haven't wired it up yet.

As Aki was telling you earlier, we need to set up the UIActivity View Controller to provide data to the extension.

So let's go do that.

Going to go into our Share Method.

And we're going to add some more code.

Setting up the UIActivity View Controller.

As you can see, as Aki talked about earlier, we're creating an activity item using our selected image URL.

And now we're going to add some code where we're just configuring the activity item as we go, as Aki talked about earlier oops.

We're adding a little bit more code to actually deal with receiving the data items right here.

We're going to take a look at make sure that we were successful.

We're going to count the number of items and we're going to grab the item provider, which is provided to us by the custom action.

And then we're going to take the image and save it back into our image collection.

And then we're going to redisplay that image inside of the collection view.

And now finally, now that we have the Activity View Controller completely configured, we're going to present it.

So, let's build and run.

This time we ought to be able to tap on the picture of the cute kids, at least I think they're cute.

Tap on the Action item.

And now we can see our custom action right here.

If we tap it, up it comes.

It doesn't do much yet, because while we're providing the data to it, we haven't actually added any code to consume that data inside of the action.

Let's go do that now.

Going into our TinySketch action, which is our target containing the custom action.

And we're going to go down to viewDidLoad, which is where we should consume the extension item.

To add something to uphold the input item from the extension context, and we're going to grab the first image item provider.

And we're going to make sure that it has an item conforming to our type identifier of a type image.

And then we're actually going to load that image.

And with the magic of NSItemProvider, we're going to it's going to be coerced from a URL into an item auto-magically for you.

We actually take a look at the signature of these block parameters and perform all the coercions behind the scenes for you.

I thought that was cool.

[ Applause ]

And then we're going to take that item, we're going to assign it to our image properties so that we can hold onto it.

And then we're also going to show it in our imageView.

Once we're done with it, I'm scrolling past, we're going to need to be able to take this data and send it back to the host testing application.

So let's set that up.

We're going to create an extension item.

We're going to check that we're not editing for the Web.

That will come later.

And we're going to attach the newly-created image, the flattened image, the image containing all the adornments, into the extension item using NSItemProvider.

This time we're just, we're not going to supply a URL, but we're going to supply an image.

And then as Aki talked about earlier, we're going to finish the request by calling completeRequestReturningItems and passing the extensionItem.

Let's build and run.

And let's edit.

Pulling it up and annotating the image.

And you can see we actually have data flowing across the wire.

We can dismiss, and the data goes back.

That wasn't all that interesting because I didn't actually make any edits.

So I'll make an edit this time.

Put an appropriate label in here, in the speech bubble.

Having fun at the park.

Hit Done. And you'll notice now our image has been replaced.

The data has made it back from our custom extension to our test application.

And we have round-tripped a custom action on iOS.

Now, while this is cool, I'm really here to show you about how this works, can work, with Safari and the Web.

So let's go ahead and enhance this custom action to actually pull its data out of a live DOM and then replace the data in that DOM when it's done.

Let's do that now.

Going back, and stop.

And now you may have seen some of my notes earlier about adding Web support.

We're going to scroll back up.

And this time we're going to add an else condition to receive a URL from Safari.

You notice we're going to have an else.

And if we have an itemConformingToTypeIdentifier KUTTypePropertyList, this is important because we're going to be sending a dictionary.

We're going to unpack the image URL out of this dictionary.

Also set up our little editing for Web flag to Yes.

And then we're setting up an NSURLSessionDownloadTask to actually perform the download.

If the download succeeds, we unpack the image data and render into a UIImage.

Again, stashing the image away in our property and assigning it to the imageView.

If we're unable to download, we'll get an NSLog.

And on the other side of it, in Done, when we're done editing the image, we need to pack it back into a property list.

And I've got some code to do that here.

You'll see that we're going to take the flattened image and write out a temporary file here.

And then we're going to actually create a JPEG string of that and pack that into a Web dictionary basically creating a data URL that Safari can then swap back into the DOM.

So, everything's wired up there.

Let's go into our JavaScript.

As I talked about earlier, we are expected to provide the run and finalized functions.

And let's do that now.

Run, we're going to iterate over all the images in the DOM.

And we're going to send them over in the imageUrls key to the custom action.

And we're also going to send the base URI.

Our custom action is only going to consume the first image, but you know, this is written for growth.

And then finally, when the data comes back from the custom action, we're going to replace the image by taking that encoded data URL and shoving it into the image using the or replacing the image source property.

Save. Build and run.

Up comes our application.

We know this works.

Let's home out.

And let's to go Safari.

I happen to have a Web server running in local host with a picture, again, of my kids just hanging out at the park.

Let's see if we can edit this image.

Hitting Annotate Image.

Up slides the custom action with the data from Safari.

Now let's edit and provide an appropriate speech bubble.

Hit Done. And now you can see there's a live replacement of the data right there in Safari using the JavaScript that I just showed you.

[ Applause ]

So, Safari Custom Actions, as you just saw, provide the flexibility of the Web married to the power of Extensions.

You can transform Web data really, really easily.

And you can float a native interface right over the top of it, which allows you to create these rich work flows that were unimaginable in previous releases.

So as Damien talked about, Extensions are secure by default.

They don't share data unless you tell them to.

And they have a one-to-one correspondence with the hosting app.

That way you have your own address space in which to make your mistakes and your action.

And the last thing we really want to encourage you to do is just to have fun with this feature.

And don't think of it as sticking new things onto things like barnacles.

Think of it as constructing these awesome new workflows that you were really unable to explore before now.

For more information, you can talk to the man in plaid, Jake Behrens.

Or I'd urge you to read our excellent documentation, The App Extension Programing Guide at

If you've never been to the Apple Developer Forums, you can interact with employees like myself, Aki and Damien there, and provide peer-to-peer support.

Thank you for coming to learn about Extensions.

And we can't wait to see what you guys do with the feature.

Thank you.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US