WWDC2017 Session 227

Transcript

[ Applause ]
>> Good morning.
Welcome to Session 227, Data
Delivery with Drag and Drop.
My name is Dave Rahardja, and
I'm joined here, today, by my
colleague Tanu, who's going to
be operating the demos.
So, if you've been following
along in our series of Drag and
Drop sessions, this diagram
should be pretty familiar to
you.
During this session, we're going
to be focusing on this part of
the diagram, Item Providers.
Now, Item Providers are
fundamental to the way data is
transferred between applications
in drag and drop.
So, in this session, we're going
to cover four topics about item
providers.
We're going to cover some
basics.
We're going to talk about how
uniform type identifiers help
your apps to be more compatible
with others in the system.
We're going to cover how you can
create model classes that work
really well with drag and drop.
And finally, we're going to
close out by covering some
advanced topics that'll add some
real polish to your apps in our
ecosystem.
So, let's get started.
Let's talk about some
NSItemProvider basics.
So, what's an NSItemProvider?
Simply put, an NSItemProvider is
a data promise.
All data is loaded through drag
and drop asynchronously and on
demand.
An NSItemProvider can also, help
you to provide progress and
cancellation of data transfer.
And it is supported widely in
our API.
Of course, in drag and drop, but
also in UIPasteConfiguration.
And we've added API for
UIPasteboard, so that you can
use NSItemProvider directly with
it.
So, what does it mean to provide
data or promise data?
Providing data to an
NSItemProvider is really simple.
All you have to do is create an
NSItemProvider and pass it an
object that is suitable for use.
Now, a lot of our system
provided classes, such as
UIImage in a string, in its
attributed string can be used
this way.
And we'll see in a few slides
now, how you can make your
classes work in this fashion, as
well.
Once you provide the data, it
can be retrieved by the
receiving application equally
simply, by calling the
loadObject method on the
itemProvider, therein.
Remember that data transfer is
asynchronous and that your
completionHandlers will be
called on a nonmain cue.
So, if you're going to update
the UI with your received data,
be sure to dispatch back to the
main queue before you call UIKit
methods.
And you're going to see this
brought up a few times.
So, to show you how easy it is
to use NSItemProviders, we're
going to start with a demo.
Tanu.
[ Applause ]
>> Good morning, everyone.
You've now heard of drag and
drop and NSItemProviders.
But now, you're probably
wondering how to use these in
your own apps.
My name is Tanu Singhal, and
I'll show you a simple demo that
will help you get started with
NSItemProviders.
We have an app, here, that has a
list of customers for our
product.
Now, I met some new people who
are also interested in trying
out this product.
So, I noted their names down in
Reminders.
It would be nice if could drag a
name and drop it into my app.
So, how do we do this?
Let's look at the
TableViewController for our
Contacts app.
Before I start typing I want you
to know that we made the sample
code for this available on the
developer website.
So, you don't have to worry
about jotting down everything,
right now.
In the TableViewController, I'll
add a TableViewDropDelegate.
Let's also set this delegate.
You may have heard of the
TableViewDropDelegate in
previous sessions.
But if you haven't, then don't
worry.
Because for now, we only need to
implement one method called
TableView performDropWith
coordinator.
In this method, I'll iterate
over the drag items provided by
the ItemProvider.
Sorry, the dragItems provider by
the coordinator.
Now, each dragItem has its own
itemProvider.
And we can check whether the
itemProvider can load objects of
a certain type.
So, I'm going to check if the
itemProvider can load a string.
For that, I'll call the
canLoadObject method.
If it can load it, then I'll
call the loadObject method.
And the completionHandler of
this loadObject method will give
me an object which will be a
string that I can use to update
my data models, as well as my
UI.
One thing to note is that the
completionHandler is on the
background thread.
So, if you want to make any
changes to your UI, then you
need to go back to the main
thread, and then make those
changes.
So, let me dispatch my main
queue.
And in the main thread I'll use
the string object, and I'll
insert it into the TableView.
This code is ready to run, now.
Wait just a moment.
And this time, I can drag a name
from Reminders and drop it into
my app.
The canLoadObject and LoadObject
methods can also be used for
other system types, like
attributed strings, URLs,
images, and even colors.
So, now that you've seen how
straightforward it is to handle
system objects in your using
itemProviders, I would encourage
all of you to try out these
APIs.
Up next, Dave will talk about
Progress and Cancellation.
[ Applause ]
>> Thank you, Tanu.
So, that covers the basics about
how to use NSItemProviders in
your application.
It was really simple.
Now, let's talk about progress
and cancellation.
When you retrieve data using
NSItemProviders, we return a
progress object to you that
allows you to track the progress
of data transfer as it occurs.
The progress object has two
interesting properties on it,
that you might want to observe.
The first, is the
fractionCompleted property,
which tells you from zero to
one, how much of the data has
been transferred.
And the second, is an isFinished
property that tells you that the
data transfer has been
completed, whether it is
successful or not.
You can use key value observing
to watch these two properties
and update your UI accordingly.
Such as by providing your own
progress indicator and
cancellation button.
There is a cancel method on the
progress object that will
immediately cancel the data
transfer, as well.
When you call he cancel method
on the return progress object,
your receiving application will
receive, will get a callback on
the completionHandler with an
error in it, regardless of
whether or not the source
application has cancelled their
data providing.
You get one progress object per
load request.
However, there's an overall
progress object that you can get
from the UIDropSession that
allows you to monitor and cancel
the entire remaining data
transfer in progress.
So, now that we've covered the
basics, let's talk about
maximizing compatibility.
You want your application to
provide data to as many other
applications on the system, as
possible.
And receive drops from as many
applications, as possible, as
well.
The key to understanding how to
maximize compatibility is
understanding uniform type
identifiers.
So, let's recap.
One NSItemProvider represents
one thing that the user is
dragging across the glass.
However, there can be multiple
representations that you can
make available for every item
that the user is dragging.
Let me give you an example.
If you're writing a vector
drawing program, for example,
you could offer a native file
format for best quality as the
thing that you're dragging.
But you might also offer
conversions to PDF, PNG, or even
JPG, so that you can drop this
item on a variety of destination
applications.
Uniform type identifiers allow
you to tag these representations
with unique strings that
identify the kinds of data that
you're putting up for the drag.
For a native file format, you
can define your own strings,
such as
com.yourcompany.vector-drawing.
And for commonly known types,
such as PDF and PNG, you can use
the symbols defined in mobile
core services to tag them.
Now, fidelity order matters.
The order in which you register
your representations should
correspond to the order of
quality that you're making
available to the destination.
Usually, the highest fidelity
would be your internal data
representation type.
And then, followed by the next
high fidelity common type, and
so on and so forth.
Now, one tip to remember when
using type identifiers to
register your representations,
is that there are uniform type
identifiers that correspond to
abstract types.
Such as data, or plain text, or
image.
Avoid these.
Instead, use concrete data types
that help the consumer of your
data interpret the bytes that
you're going to send over to
them.
So, for example, use
utf8-plain-text, instead of
plain-text.
Or probably .png instead of
image, so that the receiver can
interpret your data.
Of course, for your private type
identifier, you're free to
define your own data types and
byte layouts.
So, you've seen how these
multiple representations allow
you to maximize compatibility.
But you didn't see any of this
type identifiers being used in
our initial example code of
creating NSItemProviders.
So, how did this work together?
This is where we talk about
creating model classes that
harness the power of both
multiple representations for
compatibility.
And the simplicity of using
objects to initialize
itemProviders.
To create model classes that
work well with drag and drop,
we're going to talk about two
protocols that help you to do
this.
NSItemProviderReading and
NSItemProviderWriting.
Let's talk about the writing
protocol, first.
NSItemProviderWriting exports
data from your model object and
NSItemProviderReading imports
data and creates a model object
from a representation.
And by adopting these protocols,
you can maintain the conversion
between your model objects and
multiple representations with
your model object and not with
the UI code.
So, let's take a look at
NSItemProviderWriting.
This is what it looks like.
There are only two things that
you need to implement to conform
to this protocol.
The first is a
writableTypeIdentifiersForItem
Provider property.
This is the list of the type
identifiers that you can export
in fidelity order.
Highest fidelity first.
And then, a loadData method
which will take that
typeIdentifier that has been
requested by the other
application.
Which will call a
completionHandler, either with
the data that you've made
available or with an error.
By implementing this protocol,
the UI code can look like this
and our framework code will do
the equivalent of this, for you.
All right.
Let's take a look at the reading
protocol.
The reading protocol is, of
course, the other end of the
pipe.
It too, has two things that you
need to implement.
The first is the list of
readable type identifiers in
fidelity order, also.
And the second, is that
initializer that will take a NS
data block, a NS data object,
which should be used to
initialize the instance of your
object.
When you implement this protocol
the UI code can look like this,
a canLoadObject.
You can pass it to your vector
drawing object.
And when you call the
loadObject, our framework code
will matchmake the two lists of
type identifiers, both from the
provider and the consumer.
To find the best match to ensure
that the highest quality data is
used to create an instance of
your object.
So, in short, to make model
classes that work well with drag
and drop, you should create,
these classes should conform to
NSItemProvider reading and
writing protocols.
Because these are Objective-C
protocols, your model classes
should also inherit from
NSObject.
And when you do this, you can
use your classes and your
objects wherever NSItemProvider
is supported, drag and drop, and
UIPasteConfiguration, and also,
with the new API in
UIPasteboard.
So, to show you how this is done
in code, I'm going to ask Tanu
to do another demo.
[ Applause ]
>> Hello, again.
In the previous demo, we learned
to load simple system objects
like strings.
Now, let's see how we can drag a
rich contact card from the
Contacts app and drop that into
our app.
To do this, I've created a class
called ContactCard.
This holds the name, phone
number, and photo for a contact.
Now, I'm going to go back into
the TableView controller, and
this is the performDrop method
that we implemented, before.
Last time, we were trying to
load a string.
Now, we want to load a contact
card.
So, I'll replace all instances
of NSSTring with ContactCard.
Since we are requesting a
contact card the
completionHandler will directly
give me a contact card object,
and I can use that to insert
into my TableView.
Now, this should have worked,
right?
But the reason we're getting
errors, here, is because the
NSItemProvider does not
recognize my ContactCard class.
So, we need to do a little work
to conform to the
NSItemProvider.
Let's look at the ContactCard
class, again.
And here, I'm going to implement
the NSItemProviderReading
protocol.
This will tell the
NSItemProvider that I can read
data that it provides.
Now, I need to specify the types
of identifiers that I can read.
My ContactCard class can read
either a vCard, or it can read
plain text.
Note that these need to be
specified in descending order of
fidelity.
If we specified plain text
first, then we would always get
called back for plain text.
Even when a more rich vCard type
was available.
So, after we've specified the
types we can read, we're going
to set up an initializer where
we'll actually read the data
from the itemProvider.
One of the arguments in this
initializer is the
typeIdentifier.
So, I can use that to figure out
what type of data I received.
If I got a vCard, I'll use a
helper method to set the phone
number, picture, and all the
other information I need.
If I just got plain text, I'm
going to set the name for my
contact card.
Now, when we run this, those
errors are resolved.
This is because the
NSItemProvider understands our
class, now.
So, this time, I can drag and
name from the Contacts app on
the right, and drop it into my
app.
As you can see, we loaded the
name, phone number, as well as
the photo for this contact.
Note that we can still drag out
plain text, like from the
Reminders app, again.
And this works, because our
ContactCard class not only
handles vCards, but it also
handles plain text.
So, this is what we wanted.
And our users really like this
feature.
But now, we have another feature
request.
They want the ability to drag
out contacts from our app and
drop it into other apps.
In order to implement that,
we'll have to conform to another
protocol called
NSItemProviderWriting protocol.
As part of this protocol, I'll
specify the types of identifiers
that I can write.
My class can again, write either
a vCard or plain text.
After this, we need to specify a
loadData method.
In this method, we'll create the
data that we want to provide to
the itemProvider.
So, based on the type
identifier, I can create
different types of data and I'll
just pass it to the
completionHandler.
Now, we have completed the
implementation for the
NSItemProviderWriting protocol.
There's just one last thing to
do.
We need to go back into our
TableViewController and tell the
TableView that it can be used to
drag out items.
To do this, I'll implement the
TableViewDragDelegate.
We need to set this delegate,
too.
And for this delegate, we'll
implement one method called
itemsForBeginning session.
In this method, I'm going to
create an itemProvider.
And you can see that I'm able to
pass a ContactCard object
directly to the itemProvider's
initializer.
This works only because we
implemented the
NSItemProviderWriting protocol.
Let's run this code, now.
And I would like to share a
contact with my colleague on
Messages.
So, let me drag out Dinesh's
contact and drop it.
As you can see, we were able to
send out the name, phone number,
as well as the photo.
Which is what we were trying to
accomplish.
So, now we have learned how the
NSItemProvider reading and
writing protocols can be used to
transfer data using custom class
objects.
These are really powerful
protocols and we think you'll
find them extremely useful when
you implement drag and drop in
your own apps.
Up next, Dave will cover some
advanced topics.
[ Applause ]
>> Thanks, Tanu.
So, cool. All right.
Next, we're going to cover some
advanced topics.
These are just some collection
of things that is very good to
know, when you're going to
polish your app so that it
shines in the drag-and-drop
environment.
The first thing we're going to
cover is data marshaling.
So, if you look at the
NSItemProvider API in iOS 11,
you'll discover that there are
three ways you can provide data.
You can provide data as a data
object, obviously.
But you can also provide data as
a file or a folder on your file
storage.
Third, you can provide the data
as a reference into a File
Provider.
We'll talk about File Providers
more, later.
Similarly, the receiver of the
data can retrieve data in three
different ways.
They can copy it as their own NS
data object.
They can copy a file or a folder
into their container.
And they can attempt to open the
file in place.
So, three ways to provide data,
and three ways to consume it.
What do you have to do to make
sure that data transfer happens
seamlessly, and that the data is
made available in the correct
format?
Well, the good news is you have
to do nothing.
We do it for you.
Data marshaling makes sure that
the provider of the data can
provide and consume the
information in the way that's
most convenient for them.
If you provide a file and then,
you consume it as a data, we
will read the file into an
NSData object for you.
If you provide an NSData and you
ask for a file copy, we'll write
it to file storage and give you
your L reference to it.
If they provided a folder and
you asked for NSData, we will
zip up the contents of the
folder and give you an NSData of
the zip file.
And if they provide a reference
to a file provider and you ask
for a file copy, we will call in
the promises for the file
provider and make a copy on your
behalf.
All right.
So, we've seen how progress and
cancellation worked from the
consumer side.
So, I want to talk a little bit
about how progress and
cancellation works on the
provider's side.
I'm going to show you a block of
code, here.
Don't worry that it's a dense
block of code, because I'm going
to highlight the parts that are
most important.
This is a loadData
implementation for our
NSItemProviderWriting.
In which you are going to use a
dataLoader object to
incrementally load data.
To provide progress and
cancellation support, the first
thing you have to do is create
your own progress object.
In this case, we're creating one
with a UnitCount of 100 as a
percentage.
On that progress object, you can
attach a cancellationHandler
that will be called when the
consumer of your data calls
cancel on their instance of the
progress object.
In this case, all we're doing is
setting a local variable from
true to false, which should stop
the dataLoader from loading the
next chunk.
As the dataLoader progresses,
you can update the
completedUnitCount on your
progress object to drive the
progress indicator on the
receiver.
And of course, you have to
return your own progress object
so that we can type it over to
the other side.
All right.
Switching gears, now.
We're going to talk about some
features that help you to polish
your data representations when
creating suites of apps.
So, we're going to talk about
Per-Representation Visibility.
Remember, you can provide
multiple representations of your
data in NSItemProvider.
You can restrict the visibility
of each representation to either
visible to only your source
application.
To the applications that are
assigned by your team, so in
your suite of apps, or to
everyone.
You can use this property to
hide private types that you are
rapidly maintaining with
versions of your application
suite.
So, you don't have to worry
about third parties serializing
your data to disc.
In a similar vein, you have a
team data property on
NSItemProvider.
This is an eight kilobyte data
block that you can attach
through an NSItemProvider.
They are only visible to other
applications in your team.
You can use this to improve your
UI in any way you see fit,
during a drag.
This is metadata, and can be
retrieved even before the user
lifts their finger.
All right.
There is a suggestedName
property on the NSItemProvider.
If you provide a string for the
suggested name, we will use it
as a file name when the
retriever writes your data to
disc.
This is especially useful, of
course, when you're providing an
NSData.
And the last property I want to
talk about is Preferred
Presentation Size.
This is a CG size property that
you can provide to give a hint
to the receiver about how big
your representation's going to
end up after they've laid out.
This is used, for example, by
Mail, to drop images into the
Mail Compose sheet, into the
destination layout, even before
the data arrives.
And finally, I'm going to talk
about File Providers.
So, File Providers is a whole
other topic, on its own.
But a short summary can be said
as follows.
A File Provider is an app
extension.
This app extension allows data
transfer from a network
download, for example, to
continue even if your main
application has been terminated.
For long running drags, this is
especially useful.
Because as the user waits for a
download to occur, they may
navigate away from your app, and
your app could get terminated.
If you provide a URL to a file
inside a File Provider
container, the File Provider
extension will continue to serve
the data transfer request, even
though your UI application is
terminated.
And that increases the chances
that your data is going to be
transferred successfully.
And as an added bonus, if you
create a File Provider that
appears in the file's app, we
will allow you to drag and drop
URLs that can be opened in
place.
Which means that multiple
applications can access that
same file, instead of getting
their own copy.
There's some really great
information available in the
following two sessions.
I highly recommend that you
attend them, if you are
interested in providing File
Providers in the context of drag
and drop.
All right.
Next, we're going to take a look
at what we can do with files in
drag and drop, by having Tanu do
another demo.
[ Applause ]
>> Hope you are ready for
another demo.
In the previous demo, we saw how
we could drag and drop simple
system objects, as well as
custom class objects.
Now, let's see how we can handle
files.
We'll look at our customer's
app, again.
And for some people, here, we
have additional data.
Like for Adam, we know what
products he's purchased.
Now, I would like to drag out
this file into another app and
potentially edit it, there.
To implement this, I'll look at
the ContactDetailsViewController
in my customer's app.
Over here, we will add a drag
interaction to the attachment
image that we just saw.
Let's also, implement the Drag
Interaction Delegate, right
here, because we have set the
delegate to self.
You may have heard of the drag
interaction delegate in previous
sessions.
If you're interested in learning
more about this delegate, I
would encourage you to check out
the video for Session 213 on
Mastering Drag and Drop.
For the purposes of this demo,
though, we'll only look at one
method, called itemsForBeginning
session.
In this method, we'll create a
new itemProvider, and on the
itemProvider we'll call a method
called
registerFileRepresentation.
This method takes four
arguments.
The first one is the type
identifier.
Our attachment file was actually
an image.
So, I've set the type identifier
to JPEG.
The second parameter is file
options.
This can be used to specify if
we want other apps to open our
file in place, or if we want
them to make a copy.
We would like them to open our
file in place, so we can see the
changes they make.
The third parameter is
visibility.
This can be your own process,
your team, or all.
I've just set it to all.
Finally, we have the
completionHandler.
And we are going to pass the URL
file into this
completionHandler.
So, let's get the URL, first.
Over here, I have a helper
method that grabs the URL for my
file from a File Provider.
I need to use a File Provider
only when I'm opening files in
place.
If I wanted other apps to make a
copy, then I do not need a File
Provider.
I could have just provided a
local URL.
We'll pass this URL to the
completionHandler.
Now, our itemProvider is set up.
And we can use this to create a
drag item.
With this, the code is ready to
run.
So, now I can drag out this
file.
And let me open another app
where I'll drop it.
So, we'll drag out the data,
drop it into a document editing
app.
And I'm concerned now, that Adam
is buying a lot of products from
our competitor.
So, let me highlight this and
save these changes.
So, since our document editing
app actually used the URL
provided by the customer's app,
you'll see that we got the same
changes back in our customer's
app.
[ Applause ]
Glad you like that.
We've now seen how to handle
system objects, custom class
objects, as well as files.
We have a wide range of APIs
that you can adopt.
And we look forward to seeing
how you use them.
Back to Dave, to summarize.
[ Applause ]
>> Thank you, Tanu.
All right.
Let's recap.
During this session, we've seen
how an NSItemProvider is
fundamental to the way data is
transferred between applications
in Drag and Drop.
We've seen how multiple
representations allow your
applications to maximize
compatibility with other
applications.
And we've seen how data is
transferred asynchronously with
progress in a cancellable
fashion.
We've also seen how
NSItemProvider reading and
writing protocols allow you to
make model classes that work
well with drag-and-drop UI code.
And how visibility and team data
allows you to create suites of
apps that work in especially
well together during drag and
drop.
And finally, we've seen how File
Providers afford you the ability
to open files in place as the
result of drag and drop.
For more information, please
visit our developer website,
Session 227.
And I highly encourage you to
attend the File Provider related
sessions, today and tomorrow, to
learn more about how to create
File Providers for your own
applications.
If you haven't seen the first
three sessions on Drag and Drop,
I highly recommend that you
review them on video.
Thank you, for your attention,
and have a great WWDC.
[ Applause ]