WWDC2014 Session 205

Transcript

X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
>> Hello. I'm Ian Baird,
iOS Frameworks Engineer,
and today I'm here to tell you
about creating extensions
for iOS and OS X.
Going to lay out a quick
agenda for today's talk.
First, we're going to talk about
extensions and extension points.
Then I'm going to invite
my colleague, Matt Gamble,
from the Notification
Center Team to the stage
to tell you more and show
you more about creating one
of the hottest new tickets
at WWDC14, Today Extensions.
And then, following
on from that,
we're going to invite our
colleague, Guy Fullerton,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
we're going to invite our
colleague, Guy Fullerton,
onto the stage to tell
you more about how
to create Share Extensions
like the one you saw
in the keynote with Pinterest.
Moving along, what
are extensions?
Well, extensions are almost
like, it's a way for you
to extend the system
and other applications.
It's almost like more surface
area for you to be able
to apply bits and
pieces of your app.
As you can see here, we have a
screenshot of the Apple homepage
on an iPad showing the new
activity ViewController,
and you'll notice a
lot of familiar faces,
a lot of familiar icons in
that activity ViewController.
You can see messages, mail,
and we have a new
one, the Butterfly.
And Butterfly is a
sharing extension.
You can see it's at home right
there with its brethren right
in the midst of the
sharing extensions.
And we also support
sharing extensions on OS X.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And we also support
sharing extensions on OS X.
You can see we have the
Butterfly sharing extension
on Yosemite.
We support Notification
Center Extensions also known
as Today Extensions.
And as you can see, notification
extensions are really good
for giving you at-a-glance
information;
something where you just
want to pull your phone
out of your pocket, take a quick
look, check some sports scores,
maybe traffic conditions,
the weather,
and then put it back
in your pocket.
It gives you at-a-glance, quick
access to information you want.
And of course, we
support this on Yosemite
and you can see it looks
gorgeous blended right
over that new background.
It's just beautiful,
stunningly beautiful.
The team did a great job.
Next, I want to show you the
Custom Action Extensions.
You can see here we have our
new Annotate Image Extension,
which I'm going to show
you how to build tomorrow,
right in that row of actions.
Actions can be UI
and non-UI actions,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Actions can be UI
and non-UI actions,
as you saw with the Bing
Translate Extension.
And we support custom
actions on OS X as well.
As a matter of fact, this
is the infrastructure
for the Markup Extension
on OS X.
And you'll notice this
is a very common theme.
We're sharing infrastructure.
We're making it easy for you to
build things for both platforms.
Next, on iOS only we have
the photo extensions.
Now, if there is a photo filter
that you've been
missing-I don't know,
maybe?-or an annotation
extension you would like to make
for photos, you can build it
now right into the photos UI.
This is really cool.
And we also support
Document Providers.
Document Providers bring
storage and things like that
from the Cloud right into
applications inside of iOS.
This is a first.
It really breaks down
a lot of barriers
that we've all been living with
for, I believe, seven releases.
And next I want to tell you
about Finder Extensions.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And next I want to tell you
about Finder Extensions.
Finder Extensions
are really cool.
In the past if you wanted to be
able to badge or annotate items
and folders inside of the
finder, you may have to resort
to sort of sketchy behavior
like mock inject or some sort
of hackery and now you
don't have to do that.
Now, you actually have a
supported way to badge items
in the finder and we
think this is really cool.
And then last, but not
least, third-party keyboards.
We're supporting
third-party keyboards
in iOS now, using extensions.
So, I've told you about
extensions and some
of the extension points that
we're offering in iOS and OS X,
and next I'd like to tell you
about delivering extensions.
Extensions are delivered
as part of your app known
as the Extension Container.
An Extension Container can
contain many different types
of extensions.
For example, it could
contain a Today View Widget.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
For example, it could
contain a Today View Widget.
It could contain a
sharing extension
and it could also contain
maybe multiple custom
action extensions.
They're all bound up within
your application's bundle
and delivered via the App Store.
Extension Points-so
I've talked a little bit
about Extension Points earlier,
but I'll tell you what they are.
Extension Points, again,
are the new bits and pieces
of surface area that we've
exposed on the system
for your extensions to bind to.
For example, a Today
View Widget binds
to the Notification Center
via the Notification Center
Extension or the Today
View Extension point.
This means you're never going
to see a Today View Extension
in the activity ViewController,
for example.
And one thing to remember
about all extensions is
that they're purpose
built binaries.
See, these are not apps.
These are not even
a piece of your app.
It's not a special mode
that we launch your app in.
It's a special purpose binary.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It's a special purpose binary.
It has its own code signature.
It has its own set
of entitlements
and it has its own container.
Damien's going to speak to this
further in tomorrow's session.
So, as I said, they're not apps.
And they're always accessed
via Apple Frameworks code.
You never really
directly launch them.
Apple Frameworks code is in
charge of discovering them
for you and providing
usually the user interaction
by which they're launched, and
I'll talk about that later.
And, as I was talking
with a developer at lunch
about this right
before we came here,
these are not facilitating
app-to-app IPC.
You don't get, like, a pipe
back to the containing app
for your extension to
be able to talk to it.
If you want to build workflows
that incorporate app-to-app
IPC you can still use things
like UIApplication openURL.
But, what I would ask you
to do is reconsider some
of these workflows that you
had before that involved sort
of contextually switching from
one application to another
on a user's phone and reconsider
casting this, maybe in terms
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on a user's phone and reconsider
casting this, maybe in terms
of custom actions, which are
really the spiritual successors
to services.
So next, what are
Extension Points?
They're very high level.
They mark extensible
parts of the system.
They're always packaged
in System Frameworks.
We don't support
third-party extension points
and they combine API and
Policy, as you can imagine.
Again, with the Today
View Widget.
The Today View Widget,
it's very simple.
It's largely, as Matt's
going to tell you.
a ViewController.
But, this ViewController
also comes
with its own policy-policy,
which incorporates things
like launch characteristics.
Now, an important thing to
remember about extensions,
most extensions on the
system are bound one-to-one
with their hosting app.
So, let me give you
an example of this.
Let's say you write
a sharing extension
and the sharing extension
is invoked in Safari.
An instance of your extension,
a new process is going
to be spun up to serve Safari.
Now, if the user homes
out and goes over to mail
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, if the user homes
out and goes over to mail
and launches your
extension again-let's say,
maybe to share an attachment-a
brand new process is going
to be spun up for
your extension.
Those processes are not
going to share address spaces
and this protects you from
mistakes in your code.
If you're still using
Objective-C
and you have a wild pointer
or something like that,
you're not going to be able to
bring down all of the instances
of your extension that
are running on the system.
It's really cool.
The extension point also
governs the presentation
of your extension.
An important thing
to remember is
that extensions are
UI ViewControllers.
You're going to hear that
multiple times today.
But, UI ViewControllers
can be exposed
to the user in multiple ways.
And usually since
extensions live inside
of the user interface
of another application,
it's up to that application
and up to the system
as to how it decides
to present you.
You can either be
contained or presented.
Notification Center Widgets,
Today View Extensions are
contained while sharing
extensions are presented.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
extensions are presented.
And when many extension
points support
or a few extension points
support view controller
and non-view controller
variance of the extension point.
For instance, custom actions,
as you'll see tomorrow,
support a view controller
variant, which allows the user
to interact with the content
before it's transformed
and sent back to the host.
As you saw during the keynote,
when Craig tapped the
Bing translate extension,
there was no UI and that
was a non-view extension,
and we'll tell you
more about that later.
So, how are extensions invoked?
This is-again, going
back to our example,
we're going to show you what
happens when the user decides
to tap on the Butterfly
sharing extension.
The user taps and the
extension host, which is Safari
at this time, goes and talks
to the Notification
Center Framework.
The notification-sorry,
not notification,
the Social Framework.
And the Social Framework
goes and discovers and loads
and presents the extension.
You'll notice that Safari is not
directly launching the extension
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
You'll notice that Safari is not
directly launching the extension
at any point.
It's bouncing through Apple
Frameworks to do this.
So, in summary, extensions are
small pieces of functionality.
They're purpose built
to do one thing
and then get out of the way.
They extend the system in
new and interesting ways.
We can't wait to see what you
guys do with these things.
And they mark extendable parts
of the system, as I said,
new surface area for you
to attach the functionality
of your app in interesting and
meaningful ways for our users.
And now, to tell you more
about Notification
Center Extensions I'd
like to invite my colleague,
Matt Gamble, to the stage.
[ Applause ]
>> Hello, I'm Matt Gamble.
I'm an Engineer on the
iOS Notifications Team,
and today I'm going to tell you
about Notification
Center Extensions,
or widgets as we call them.
Now, we've had widgets
in some fashion
on the system for a while now.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on the system for a while now.
But, for the first time in iOS
8 we are giving developers the
power to add their own content
to the Notification
Center's Today View.
Now, perhaps most fundamentally
widgets are view controllers
so that means everything
you know
about view controllers
including the lifecycle
and containment behavior
in API will serve you well
when you're constructing
your widget.
For example, the
appearance calls.
These will tell you when your
widget is coming and going
in the Notification Center.
And of particular
interest is viewWillAppear,
as you want to be sure that your
view is up and ready to be used
by the user by the time you're
returning from viewWillAppear.
So, speaking of getting
ready, it's important
that you get ready fast.
Performance is a very
big consideration
with Notification
Center widgets.
So, a couple tips.
Be sure to load cached
data, so you're bringing
up your view quickly
and you're ready to go.
And if you have some
new data that comes
in you can do your
own custom transition.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Also, kick off your expensive
operations as early as possible
and definitely in
the background.
And when those expensive
operations return be sure
to cache this data.
So, layout is another concern.
Of course, your widgets
and your widget has control
over its sub view hierarchy and
laying all of your content out.
But, just as with any other
parent-child view controller
relationship, the Notification
Center will be laying
out your views frame.
So, that means that the
Notification Center is setting
the frames.
You don't set your own
widgets views frame.
But, of course, not all
content is created equal.
So, if you might have,
need some more room
for your content you can
indicate your preferred height
and you can do this in two ways.
The first is through
auto layout.
If you can describe
your widget's height
with constraints then those
will be honored automatically
in the Notification Center.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Otherwise, if you're
using more manual layout,
you can use the view controller
method setPreferredContentSize.
And both of these systems
work across both iOS and OS X.
So, while you will definitely
want to set your size as early
as possible to ensure
that you're showing your
content the right way
in Notification Center,
that's not the only time
that you can call it.
If, for some reason, you are
showing different content
or changes you can
call this later.
For instance, let's say
that we had a button
that would show a
little bit more content
or a little bit more
detail about some content
that we have in our widget.
You could tie this up to this
action, calculate the new height
for this new content
and then just
call setPreferredContentSize.
The Notification Center
will take care of this.
It'll automatically
resize your widget,
but that's only half the story.
What you probably
want to do is ensure
that your content animates
along with this transition
and you can do that on iOS
with the new method
viewWillTransitionToSize
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with the new method
viewWillTransitionToSize
with TransitionCoordinator.
So, if your widget implements
this method it'll get called
and pass in the
TransitionCoordinator
and with this coordinator
you can call
animateAlongsideTransition
passing in your block
with your animations and
these will be run in parallel
with the systems resize
animation, and then again,
and then a completion to know
when everything's finished up.
Now, there's a similar
story on OS X.
In this case it's
viewWillTransitionToSize.
Again, this will be called
and you can take advantage
of NSAnimationContext to
run your own animation group
and again pass in a
completionHandler so you know
when everything is finished up.
Now, new to iOS 8 is a
protocol, NCWidgetProviding.
And one of the optional methods
in this protocol is
widgetPerformUpdateWith
CompletionHandler.
And it's important to implement
this one, as it'll ensure
that your widget's
content stays up to date.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that your widget's
content stays up to date.
So, if this is implemented
you will be passed a
completionHandler, you want
to perform your own update
and then determine what
is the appropriate result
to pass back in that block.
So, if you have some
new data go ahead
and use NCUpdateResultNewData.
If there's nothing new
go ahead and use no data.
And if something went horribly
wrong, pass failed and we'll try
to leave well enough alone.
And then finally, be sure to
call the completionHandler
with the result that you have.
Alright, let's take
a look at this.
So, some of you may remember
from last year we
had a presentation
and we showed off an awesome iOS
application for staying in touch
with all of the clowns that
are in your clown network.
We called it ClownTown.
Well, we're going to take this
application a little bit further
by adding a widget for
the Notification Center.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, adding a widget
is as simple as opening
up an existing application
project and adding a new target.
For both iOS and OS X you'll see
there's an application extension
section and you can just
select the Today Extension.
But, I've actually
already started work on one
so we're going to go right
into our view controller here,
because widgets are
ViewControllers.
And the first thing I'm
going to do is ensure
that my widget has
the right height.
So, I'm using an
Interface Builder storyboard
for my interface.
So, awakeFromNib is
a pretty handy place
to take care of this.
So, after calling
super and then setting
up a little state
I'm going to go ahead
and get the most recent posts.
Once I've gotten those
I'll update my table view.
This doesn't need
to be animated.
We're just initializing here.
And then the important
step is just making sure
to set my preferred content
size to, in this case,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to set my preferred content
size to, in this case,
just the content size
of my table view.
So, this will make sure that
we're initializing the state
where we're showing a
single post in our widget,
which is nice, but perhaps you
want to see a little bit more
of what's going on in
your clown network.
Well, how about we add a button
and an action for our button
to show a little bit more.
So, the first thing we'll
do is update our state
and then calculate our
preferred height, which,
since we're using a table view,
we want to count the number
of rows and also make sure to
account for our footer view.
Once we have our
preferred height we want
to set our preferred
content size
and then make sure we
tell ourselves that we had
to do an update in the future.
That'll come in handy later.
So, again, we've set our
preferred content size
so this will be sure
to animate the widget
to the appropriate height.
But, we want to make sure
that our content comes
in in a really nice
animated fashion.
And so to do that
we'll implement
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And so to do that
we'll implement
viewWillTransitionToSize
withTransitionCoordinator.
And so, if we have a
coordinator then we know
that this is an animated
transition
and so we'll call
animateAlongsideTransition
completion.
So, we'll make sure to
update our table view
in an animated fashion and
in the completion be sure
to update the title
of our button.
Then, if for some reason we
didn't get a coordinator,
then we'll just update our
table without animation
and then update the button.
So, this will make sure that
our adding of our rows comes
in a nice animated way alongside
the system animation that's
resizing our widget.
Now, one more thing I want to
do before we give this a try
and that is making sure that
our content stays up to date.
So, I'm going to implement
the method widgetPerformUpdate
withCompletionHandler.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, I'll start by getting the
recent, most recent posts,
make sure I get any updates,
and then calculate the result.
So, if we already know that
we needed to do an update
or if we actually did get
some new content then we want
to use NCUpdateResultNewData.
Otherwise, we can just
go ahead and use No Data.
So, if we actually did
get some new data we want
to do a couple things.
We'll definitely want to
update our table view to ensure
that it's reflecting
our most recent content
and then update our
preferred size to ensure
that our widget is
the right height.
And in either case we certainly
don't need any more updates
and we'll call our
completionHandler
with our result.
Alright, I think we can go
ahead and give this a shot.
So, I'm going to go ahead and
build and run for the simulator.
I'll switch over to
the simulator now.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I'll switch over to
the simulator now.
Oh, and here we have our
ClownTown application.
Well, I'm going to
home out of this
and present the Notification
Center.
Go to the edit menu and
our widget is not there.
Excellent, let's give
that one more shot.
[ Clicking Sounds ]
Oh yes, there we go.
[ Clicking Sounds ]
There's our application.
Bring down this, one
new widget available.
There it is.
[ Applause ]
Alright, so hit done
and here's our widget.
Well, we can see that Corbin
the Clown has some multicolored
suspenders he's giving away,
got to make sure to
take care of that.
Alright, now here's
our More button.
If we tap that, get some new
rows animate in nicely-wow,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If we tap that, get some new
rows animate in nicely-wow,
lots of weddings happening.
Less collapses everything back
down in a nice animated fashion.
This is looking pretty good.
Well, I definitely
want to know more
about these multicolored
suspenders, so I'm going
to go ahead and-huh, okay.
Well, we've definitely
seen-if you've looked
at the system widgets it's
a pretty common pattern
to tap something in the
widget and then transition
to the application and
maybe see some more details
or get some more content.
Well, that's definitely
something
that we're interested in doing.
So, let's return to Xcode.
I'm going to stop the debugger.
And since I'm using a
table view I'll want
to implement tableView
didSelectRowAtIndexPath.
So, after we make sure we
have a valid post I'm going
to take advantage of an object
called the extensionContext.
Now, the extensionContext is an
object that you'll have access
to in your widget or in any
extension that's running
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to in your widget or in any
extension that's running
in a host.
And it has-tells you
some interesting things
about what's happening
in your current host.
It also has a couple of
really interesting methods.
In our case we're
going to take advantage
of openURL completionHandler.
Now, I've defined a custom
URL scheme that's shared
between my widget
and my application
so I can construct the
URL with this scheme
and then add some identifying
information for my post.
And then one final thing I'll
want to do is just make sure
to deselect this row so it's not
selected the next time the user
pulls down Notification Center.
Alright, so let's build
and run this again.
And here's our application.
Let's present here, add this
back, and here's Corbin again.
So, let's see.
If I tap this, hopefully
I'll be able
to get some more information
about those suspenders.
There we go.
It brings us right
to the application
and get some more detail
and make sure we don't miss
out on those suspenders.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Background Sounds]
[ Applause ]
So, a few things
to remember-widgets
are ViewControllers.
So, everything you know about
ViewControllers and the API
and the behavior is going
to serve you really well
when you're constructing
your widget.
Also, be sure your
widgets resume immediately.
You don't want the
users bringing
down Notification
Center and tapping
and not having anything happen.
Now, while the Notification
Center handles the layout not
all widgets are created equal.
You'll want different
heights, and you can so this
with the preferred height
with either constraints
and auto layout or explicitly
through preferredContentSize,
and this is across
both platforms.
Also, you want to be sure to-if
you animate your content along
with the resize animation, this
again, we have APIs on both iOS
and OS X to help you do this.
And then be sure to handle
update requests to make sure
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then be sure to handle
update requests to make sure
that all of the content
looks up to date.
Now, I'd like to invite
Guy up on stage to tell us
about Share Extensions.
[ Applause ]
>> Hi. So, my name is Guy
Fullerton and I am an Engineer
on the iOS Social and
Accounts teams, and I'm going
to give you everything
you need to know
to implement Share Extensions.
So, Share Extensions are
a way to take the sharing
and upload functionality in your
app and package it up and get
that presented in the standard
activity and sharing affordances
in the operating system.
Actually, let's go back.
So, like, the UI activity view
and the share menu in Mac OS X.
So, this is particularly
useful for social network apps
or for apps that like to
do video or photo hosting,
for example, and
lots of other stuff.
So, let's go through
a concrete example.
Let's say I've got a
photo blogging application
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Let's say I've got a
photo blogging application
that normally allows my user
to launch the app, pick a photo
from the photos library,
annotate that a little bit,
maybe make a-choose an
audience to present it to
and then upload it
to a photo blog.
But, wouldn't it be great if I
could do that upload directly
from within photos app?
Well, that's what a Share
Extension would let you do.
So, let's say I'm in photos,
I select a photo that I like,
tap the share button, up
pops the activity view
and you can see my Photo
Blog application icon there
in the activity view.
Tap that and up slides a
compose sheet for Photo Blog,
lets your user type some text to
annotate it, choose an audience,
whether they want to limit
the exposure of that photo
and whatnot, and then post it.
So, we're going to
hammer this home a lot
in the next couple days,
but a lot of extensions are
basically just ViewControllers.
That's where most of the work is
and you're probably already
familiar with how to do that.
But, Share Extensions have a
couple other concerns having
to do with their packaging,
specifically the
extensions Info.plist,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
specifically the
extensions Info.plist,
so let's talk about that.
So, normally when a Share
Extension shows up in one
of these sharing affordances you
generally want that extension
to have the app's name.
That's going to be the thing
the user's most familiar
with looking for when they
want to select that extension.
But, some applications may have
multiple share affordances.
The Photo Blog, for example,
might want a basic
posting share functionality
and maybe it wants a way
to specifically set the blog's
header photo, for example.
So, it's important to be able
to customize your
Share Extension's name.
And the way you do that is
with CFBundleDisplayName
in your Info.plist.
Just set that to the name
you want your Share Extension
to show up with and the
sharing UI will display
that name appropriately
for your extension.
So, the next thing to talk
about are activation rules,
and this is probably best
explained through an example.
Let's say you're in photos
and you have a set-a
trio of things selected.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and you have a set-a
trio of things selected.
You've got two photos
and a video
and the user wants
to share that.
So, they tap the share button,
up pops the UI activity view
or the share menu on OS X
and behind the scenes we've
created an extension context
and that extension
context is the conduit
that your extension
will use to pull data
from the host application
into itself.
So, this extension context
for this particular scenario
has three things on it.
It's got a reference to two of
those images, and of course,
a reference to the
video as well.
Now, the user might a bunch of
Share Extensions on their system
and each of them
wants to get invoked
in different kinds
of situations.
And so the activation rules are
what lets the system make the
right decision about
which extensions to put
in the activity view or the
share menu based on the kind
of data that's in the
extension context.
So, there are two ways
for your extension
to supply its activation rules.
The first is a predicate.
Now, every extension's
Info.plist has an
NSExtension dictionary.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And within that NSExtension
dictionary is
the NSExtensionAttributes.
The system uses those
attributes as part
of its decisionmaking process
about which extensions to show.
But, importantly for Share
Extensions the NSActivationRule
within the NSExtensionAttributes
is what you need to set.
If you set that to a
string that's a predicate,
we will run that predicate
in the host app and figure
out if your extension
is appropriate.
And that predicate can be as
detailed as you need it be
or it can be simple
if you want it
to be-whatever you need
to get the job done.
And this will serve the
needs of a lot of extensions.
Other extensions, however,
may have a little
more fine grain needs.
What we found internally
is that a lot
of our extensions just wanted to
know basic or just wanted to say
that they support a
basic set of images
or videos or some URLs or text.
So, we offer these condensed
rules that allow extensions
to specify just those
particular things without having
to write a complex predicate.
Now, ultimately these
condensed rules boil
down to a predicate
behind the scenes,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
down to a predicate
behind the scenes,
but you don't need
to worry about that.
So, to specify condensed
rules you still put
in an NSActivationRule inside
your NSExtensionAttributes.
But, this time it's a dictionary
and there are a number
of keys you can put
in the dictionary
that indicate the
amount of that type
of data your extension
is interested in.
Now, the bottom of those two
activation rules both had to do
with web content and
they're slightly different.
We have a WebURL support and a
WebPage support and I want to go
into the details about these.
So, some Share Extensions
are all
about taking the link you're
currently looking at in Safari
and posting it someplace
else to some feed
so somebody else can
click on that link
and go see the webpage.
For those kinds of Sharing
Extensions you want to specify
that you support the
WebURLWithMaxCount.
Now, another class of Sharing
Extensions are all about looking
at the page that Safari's
currently got displayed
and pulling data
out of that page.
And for those Sharing Extensions
we have the WebPageWithMaxCount
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
activation rule.
Now, in order to support
the WebPageWithMaxCount your
extension also needs to
supply a JavaScript that's run
to determine what part of the
web content to actually stick
on the extension context.
I'm not going to go into
detail on that here,
but in tomorrow's session
we dive into detail on that
so please check that
session out.
Hammering it home yet again,
extensions are basically just
ViewControllers so it's a lot
of familiar territory.
Share Extensions
can be implemented
with two kinds of
ViewControllers.
We support them on
both iOS and OS X
and you can subclass
UIViewController
or NSViewController as you wish.
You probably already
have ViewController code
in your apps today that you can
repurpose for a Share Extension.
Sure, maybe you want to
make some tweaks to it
to conform better to maybe the
limited amount of space you have
on the screen or whatnot.
But you can take that existing
code, massage it a little bit
and deploy it in an
extension, and you can get it
to look exactly how you
want using your own branding
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to look exactly how you
want using your own branding
or existing UI or whatever.
Some extensions,
however, want to conform
to the standard system
share sheet look.
And for them we offer the
SLComposeService ViewController
that you can subclass to
get, among other things,
the standard look, the standard
animations, text editing,
a indication of the
remaining characters based
on the characters typed so far,
the post and cancel buttons,
built-in previewing and
other limited amounts
of customization.
So, once you've implemented your
ViewController the next thing
you need to be concerned about
is actually doing the upload
or post to wherever you're
sharing this data to.
The key here is that you
need to use NSURLSession
with a background session
configuration, reason being,
your extension does not live
beyond its presentation-at least
not very long, and we'll talk
more about that tomorrow.
So, you need to make sure
that by using a background
NSURLSession you're allowing the
system to handle
the upload for you.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, you create a background
NSURLSessionConfiguration,
build an NSURLSession around
that, build an NSURLRequest
that encompasses the items
that are being shared
and create an upload task
wrapped around that NSURLRequest
and then start that upload task.
Once you've done that
the system is going
to handle the upload for you.
At that point your extension
needs to tell the host app
that it's done and it can
tear down the presentation
of your ViewController and you
do that by calling a method
on the extension context called
completeRequestReturningItems
completionHandler.
If the user chooses
to cancel your sheet,
or whatever you're
presentation happens to be,
there's another extension
context method you need to call.
This is called
cancelRequestWithError.
So, Matt already
talked about performance
with request-with respect to the
today widgets and I want to talk
about that in a little
bit different way
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
about that in a little
bit different way
in terms of Share Extensions.
Share Extensions often
have to grab a bunch
of data from the host app.
They might be dealing with 10
photos or an enormous video
and it can be expensive
to be pulling
that from the host app while
the presentation is happening.
So, if you notice that your
presentation animation is
stuttering you may need to
defer some of that heavy lifting
until after the presentation
is completed.
If you subclass UIViewController
or NSViewController,
do whatever makes sense
for the rest of your code.
Just defer that task so it's
not impacting the animation.
If you subclass SLComposeService
ViewController, however,
we will call your subclass'
presentationAnimationDidFinish
method giving your
ViewController a good hook
with which to start
that heavy lifting.
So, let's take a look
at the Photo Blog app
and the Photo Blog Share
Extension real quick.
Before going into
the code I'm going
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Before going into
the code I'm going
to show you the Share
Extension running so some
of the code will make a
little bit more sense.
So, we're going to go into
photos just like that set
of screenshots I showed.
Let's pick a different one.
Puppies-we'll do just one.
Oh, let's not do that.
There we go.
Click on the Photo
Blog Share Extension
and up comes the compose view.
So, this is clearly using the
standard system compose sheet
look derived from
SLComposeService ViewController,
and it can do a couple
different things.
You can type some text
and as I'm typing text,
it's kind of hard to see, but
there's a little indicator
of the remaining characters,
and once I type too much
text it goes negative
and the Post button disables.
This is all happening
more or less for free.
You'll see some details
when we look at the code.
The image preview there,
it happens for free,
the base class will
do that for you.
We also, this particular
subclass adds an audience picker
so this Photo Blog supports
the notion of posting publicly
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
so this Photo Blog supports
the notion of posting publicly
or only to my friends
or completely privately.
And you can choose a
different audience.
Let's delete some
text here so we're
within the allowed
range, then we can post.
So, let's go back into Xcode
and see how we implemented that.
So, the first thing I did-and
it's hard to see, sorry,
but this is as good as I
can do with the Info.plist.
The first thing I did was
set the bundle display name
for my extension to
Photo Blog and then I set
up an activation rule.
This particular activation rule
says my Share Extension only
wants one image.
That's all you need to give me.
I don't care about
anything else.
This is one I'm relevant.
And now my ViewController.
So, my ViewController subclasses
from SLComposeService
ViewController.
Now, let's go look at
the implementation.
But, before I start adding
code to this, I want to talk
about a couple artificial
constraints for this demo app.
So, for purposes of showing off
the remaining characters count
and generally updating the
Post button, I'm going to say
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and generally updating the
Post button, I'm going to say
that my photo blog has two
constraints associated with it.
The first is that no photo
can be posted with more
than 20 characters alongside
the photo, because it's meant
to just let you see the photo
and not get distracted
by the text.
And also it's supposed to
be a lightweight photo blog.
It doesn't want to spend a ton
of storage space on the photos.
So, no photo that you post can
be bigger than one megabyte.
And this last constraint
is particularly important,
because when you pull data from
the host application you're not
in control of the data's size.
If you've got a photo coming
from photos app it
might be quite large
and therefore it's your
extension's responsibility
to down sample that
image data to fit
within your size constraints.
So, let's start implementing
the ViewController.
So, I'm going to add an image
data property to my subclass.
This is going to be the
fully down sampled image data
that I'm going to ultimately
upload to the photo blog.
I'm going to keep
track of the audience
that the user wants
to post it to.
And I'm going to keep
a configuration item
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And I'm going to keep
a configuration item
that I'm going to talk
about a little bit later on.
Basically, this represents
that AudiencePicker table cell
down in the bottom
of the compose view.
Alright, so in viewDidLoad
we're going
to initialize the audience.
We already have a
utility routine in our app
that knows how to fetch the
right defaultAudience based
on a number of factors.
So, we're just going to
reuse that code from the app.
Now, those constraints
I was talking about,
the 20-character limit
and the down sampling,
both need to affect whether
the Post button is enabled.
So, if the user has typed more
than 20 characters we don't
want them to be able to post.
If the down sample is
not finished, likewise,
we don't want them
to be able to post,
so we need to make sure
the Post button is disabled
in those cases.
Also, since we're using the
remaining character count
indicator we need to make sure
that's up to date at all times.
The right place to do that
is the isContentValid method.
The SLComposeService
ViewController calls your
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The SLComposeService
ViewController calls your
subclass' isContentValid
method whenever it needs
to update the status
of the Post button
so it gives you your
chance to do those things.
So, let's implement
some of that.
The first thing I'm going
to do is look at the number
of characters that the
user has typed so far.
SLComposeService ViewController
has a content text property,
which is the text that
the user has typed so far,
so we grab its length.
Next, we calculate the number
of characters remaining based
on our Photo Blog's constraints.
Then we set SLComposeService
ViewController's
charactersRemaining property.
This is an NS number
and by setting
that to an NS number the base
class automatically updates the
text on the compose sheet
to-sorry, to reflect the number
of characters you
still have left.
And I just add it here.
So, next we are going to say
that the Post button
should be enabled
if the user has not
typed too much text
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
if the user has not
typed too much text
and if we already have our
fully down-sampled image data.
Otherwise, don't
enable the Post button.
Now, we got to get our fully
down-sampled image data and,
like I said, this can be a
potentially expensive operation,
because we don't know
how much data is going
to coming over from the host.
So, we want to defer that to
presentationAnimationDidFinish.
This method is called by
sub-called on subclasses
of SLComposeService
ViewController
after the compose sheet
has slid up onscreen.
So, the first thing
we're going to do is look
at the extension context again
and in particular look
at the input items.
Now, I'm going to go off
in the weeds a little bit
here, but bear with me.
The extension context can have
multiple input items on it.
Each input item represents
a separate upload blob.
For most Share Extensions
you only care
about doing one upload
at a time.
But, you might imagine a
particular Sharing Extension
that's allowed to do five posts
to one social network at one go.
So, for that kind
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, for that kind
of hypothetical Share Extension
you're going to need to look
at all the input items.
But, since this Photo
Blog extension only cares
about posting one photo
at a time with one post
at a time we just look
at the first input item.
Next, we're going to iterate
over all of the attachments
on that extension item.
Each extension-each
post request could come
with multiple pieces of data.
Like, you could go into
photos and select two photos
and choose to share that.
You would have an
extension context
with one item with
two attachments.
So, we're going to iterate
over the attachments
and find the photo
that we care about.
So, what we do is we iterate
over all the item providers
in the extension item
and we call hasItemConforming
ToTypeIdentifier
passing kUTTypeImage.
This says, 'Hey, item provider,
do you have an image in you?'
And if we find out
that the answer is yes,
we are going to do some work.
We're going to need to pull that
particular item provider's data
and we're going to want to do
that on a background cue so as
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and we're going to want to do
that on a background cue so as
to not disrupt the main thread.
And since we've found
the one image
that we support we can
stop iterating now.
So, the actual work of
pulling the data happens
with the
LoadItemForTypeIdentifier
options completionHandler
method, again,
saying 'Give me some
UTTypeImage, please.'
And the completionHandler
that you pass actually has
a flexible first parameter
that indicates the way you would
like the item provider
to supply your data.
In this case, my Share Extension
is interested in NSData,
so that's what my
completionHandler's
prototype is.
And when I get the-scroll
up for me,
let's see if I can make
this a little wider,
not wrap so much It's
important to note
that loadItemForTypeIdentifier
completes on an arbitrary cue
and since we're going to do
view controller work I'm going
to need to dispatch
async onto the main cue
to actually do that work.
Then I'm going to call a method
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Then I'm going to call a method
on my subclass called
imageDataLoadDidFinish
and pass it that data.
So let's go implement that.
So, my application already
has Downsampling code, right?
And it's what it would
in the normal case
in the normal application case.
So, I'm just going to
repurpose that and ask it
to downsample the image
data, passing my map size.
And that's going to complete
on an arbitrary queue,
so once that completes I'm going
to do view controller work,
so I dispatch async
onto the main queue
and then I remember the
downsampled imageData.
Now that I've got that image
data, I need that Post button
to get updated, and the way
I do that is call a method
on SL composer ViewController
called validateContent.
ValidateContent calls your
subclasses IsContentValid method
and gives it another chance
to determine whether or not
to enable the Post button.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Okay, so, when the user
taps the Post button
on an SL composer
ViewController subclass
that subclasses didSelectPost
method.
So let's react to that.
The first thing we're going
to do is perform the upload
and after that we
need to tell the host
that our extension is done
and you can dismiss
our ViewController.
And, again, we do that
by calling a method
on the extension context called
completeRequestReturningItems
completionHandler.
So let's look at
the upload again.
So the upload is going
to be very similar
to what I already showed
on the slides, but let's go
through it again because it's
very important for extensions
because they have
a short lifespan.
The first thing we're going
to do is create a background
NSURLSessionConfiguration,
create a NSURLSession
from that configuration
and create a request
representing the upload.
Now, again, this is code
we stole, well, not stole,
but code we borrowed
from the main application
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that builds an NSURLRequest
from the photo data
from the content text that the
user has typed and the audience.
Build an UploadTask
around that request
and then start that upload.
Now, in this particular
case-well, let's see,
let's step back for a second.
So, we implemented
didSelectPost method.
SL compose service
ViewController will also call a
DidSelect cancel
method on your subclass,
but in this particular case
we don't need to implement it.
The default implementation of
didSelectCancel will go ahead
and cancel the extension
context for you automatically.
But if your extension needs to
do some cleanup work in response
to a cancel, you can
override didSelectCancel.
So that's enough to handle the
basic uploading and interaction
with the sheet, but
at the bottom
of the sheet-remember there was
that little audiencePicker cell.
I'll show you how
to implement that.
The base class will call your
subclasses configuration items
method as a way to give your
subclass a chance to supply
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
method as a way to give your
subclass a chance to supply
that set of table cells to put
on the bottom of
the share sheet.
So, we're going to implement
the configurationItems method.
It's our subclass'
responsibility
to return an array
of SLComposeSheet
ConfigurationItems,
one for each configuration
item in the table.
Now, the example here only
has one configuration item,
but you might imagine a share
extension that needs multiples.
In fact, some of the built-in
system extensions have multiple
configuration items
on the bottom.
So, we instantiate one
and we set its title.
The title is what's displayed on
the left side of the table cell.
Then we set its value,
which is the audience,
displayed on the right
side of the table cell.
And then we need to react to
when that table cell is tapped.
And the way we do that is
by setting a tapHandler
on the configuration item.
Now, I'm going to avoid a retain
cycle between my view controller
and the configuration item
by capturing a weak reference
to myself inside the tapHandler.
And then what we do is we
grab a strong reference and do
that weak strong dance.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that weak strong dance.
And if our view controller
is still around.
At that point we know the
table cell has been tapped.
We want to show that
audiencePicker UI.
So we've already implemented
inAudienceViewPickerController.
It's just a basic table
view controller that happens
to be audience specific
for this particular use.
So we instantiate that.
We set ourselves
to be its delegate.
Our custom
AudiencePickerViewController
class has a delegate
method that's called
when the user taps an audience,
so that we get to react
to the specific audience chosen.
We're going to tell it what the
currently selected audience is
so it can check the right thing.
And then we're going
to call a method
on
SLComposeServiceViewController
telling it to please animate
this other ViewController
onscreen and that method is
pushConfigurationViewController.
So, this slides your
new view controller
in a navigation controller-like
style
and it resizes the
composed sheet
to fit your view
controller's desired size.
And then, finally, now
that we've implemented
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then, finally, now
that we've implemented
that we can return our
single configuration item
from configuration items.
All right, so here is our
delegate method that is called
when the
audiencePickerViewController
sees the user tap a cell.
We need to react to that.
We're going to remember
the selected audience.
We're going to tell
our configuration item
that hey, it's got a new value.
And by doing that the table cell
is updated automatically showing
the new audience.
And then finally we
want to auto dismiss
that
audiencePickerViewController.
You don't have to do this.
It really depends on what your
configuration item's needs are.
But generally, for this kind
of simple audiencePicker,
once you tap a cell you want it
to swipe away revealing
the main compose sheet.
So this is just some
boiler-plate code
for executing code
after a short delay.
And then we call
popConfigurationViewController.
So, in a navigation
controller-like way,
this says remove the topmost
view controller from the stack,
and since we only had one there,
it restores the default
share sheet appearance.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
it restores the default
share sheet appearance.
So, that is it.
Let's go ahead and
rerun this guy
and take a look at
it one more time.
Photo Blog app.
Let's home out and
go back to photos.
There's our photo.
We can bring up the photo blog
extension and there it is.
We can choose an audience,
make our comment and post away.
So, that's how you make a
share extension for iOS.
But if you are looking at that
Xcode project closely you can
see that there's a couple
OS X targets in there.
Now, it turns out
that share-yeah,
let's hide some more
stuff-that share extensions are
very portable.
It was pretty easy for me to
take my iOS share extension
and massage the code
a little bit for OS X
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and massage the code
a little bit for OS X
and so that is working here.
I can go to some photos and
select a photo, preview it,
bring up the share menu.
Hey, there's my photo blog item
and there's my OS X photo
blog share extension.
[ Applause ]
So, that demo was
all about a subclass
of SLComposeService
ViewController.
But we know a lot of extension
developers are going to want
to just subclass
from UIViewController
and NSViewController
and we'd love you to do
that because you can go to
town and customize the UI
and put your branding
in and what not.
But the basic work that you need
to do beyond implementing the
view controller is pretty much
what I already showed
you in the demo.
You've got those
Info.plist concerns you need
to still set your display name.
You need to still provide
your activation rules
and you still need
to do your upload
with NSURL background session.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with NSURL background session.
So, you just learned all
about app extensions.
You learned a little bit
about how they're packaged,
about the different
extension points,
how the communication
works between the host
and the extension itself.
You learned how to
make a Today widget
and a share extension
on both iOS and OS X.
So go take a look
at your application,
figure out what pieces of
your sharing functionality
or other functionality
you can package
up into an app extension
and deploy.
So, we think this is
really cool technology
and we're really looking forward
to all of the good features
and functionality you're
able to supply users
with your app extensions.
[ Applause ]
Jake Behrens is our
super Evangelist.
We have a really comprehensive
app extensions programming guide
that goes into tons of detail
on all the share points
and all the mechanics
of all this.
Tomorrow, we have another great
Extensions session where we go
into detail on more
share points.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
into detail on more
share points.
We also talk about more of
the underlying infrastructure.
We also go into great detail
on the JavaScript
support for web content.
And importantly, more detail
on the extension context
in NSI to providers.
So there's tons of
good information.
I urge you to check those out.
We have two labs where you can
talk to us Apple engineers.
We'd love to get you
started implementing your
share extensions.
One of those labs is
immediately after this.
So, please meet us downstairs
and we'd love to chat.
Thank you.
[ Applause ]