Dive deep into the new Drag and Drop APIs in iOS 11. Learn what users will come to expect of your draggable views and how to best deal with the asynchronous nature by which data gets dropped into your app. We'll also show you how to make your Drag and Drop look great using the advanced visual appearance tweaks that we offer.
[ Background Conversation ]
I'm glad you could make it.
My name is Robb, and together
with my coworkers, Wenson and
Tom, I'm going to take you on a
deep dive today through the new
eight drag-and-drop APIs we are
introducing in iOS 11.
So, we have a bunch of new APIs
for you, but I don't want you to
Even though we have a lot of
ground to cover, you can
gradually adopt these APIs and,
in fact, if you are one of the
few people who use Collection
view or Table view, there is a
dedicated session for you
tomorrow in Hall 2 that you can
check out, and it's followed
back-to-back by one on
If you ever wondered what the U
in UDI stands for, do not miss
This session, however, is going
to split in two halves.
First, we're going to talk about
the drag side of things, the
drag interactions, it's
delegate, the session, the
associated drag item and
previews, and then Tom is going
to take over in the second half,
and do a similar thing for the
So, we have a lot of stuff to
talk about, and we're going to
start with advanced drag
So, as you already know, drag
and drop on iOS is not only a
way to share data between
applications, it's also a fluid
user interaction, and with these
kinds of complex user
interactions, consistency is
key, and you achieve this
consistency by using
You can take one of those and
install it on any of your views.
You don't have to subclass
anything, or even worse, go in
and change the existing
superclass of the custom views.
Just install one of them, and it
will do the necessary gesture
bookkeeping for you.
You do, however, have to
at least return a UIDragItem.
You can [inaudible] into a bunch
of notifications about lifecycle
and perform some animations, and
we'll go over those in a second.
If none of this makes any sense
to you, I would recommend you
catch up on Introducing Drag and
Drop, which is a session we had
yesterday, so if you missed it,
you will have to check out the
video, and that will kind of
cover the basics.
However, if the basics were good
enough, you wouldn't be here
today, so let's look at some of
the behaviors that native
applications in iOS 11 employ,
and what your users will come to
expect of your applications,
So, here you see me in Mail, and
you can see that, as I start a
drag with a long press on a
message, I can then tap
subsequent messages and they
will flock to the drag session
that I have already in progress,
so the little batch count keeps
This is not something you get
out of the box for free, but
it's not very hard to implement,
either, and I'm going to show
So, in this example, I've
specifically the required method
itemsForBeginning session, in
which I create an NSItemProvider
for the message I want to drag,
then I create a UIDragItem with
said itemProvider set to
localObject to the message, so I
can later easily refer back to
the message, and then I return
the dropped-in array.
Now, you could use the exact
same implementation for
session withTouchAt point, and
that would work.
However, there is some edge
cases that I want you to think
about as you implement flocking
by opting into this method.
First, if you implement this
method, your dragInteraction can
now flock with any other
dragInteraction in your
application, not with
dragInteractions in other
applications, because we
currently don't allow
cross-application flocking, but
that Bob's implementing in that
other department, you can
potentially flock with that, so
you want to be aware.
In this case, I decided that I
only want to flock my messages
with other messages, so I
iterate over all the items in
the session, and if any of them
does not have an NSItemProvider
that has a UDI conforming to
this type, in this example,
private.example.mail, I'll abort
by returning an empty array, and
this will give other gesture
recognizes to recognize the tap
that triggered this flocking
Similarly, by default, the same
dragInteraction, therefore the
same UI view can flock multiple
We can't possibly know if that
makes sense for your
application, or if maybe
different regions inside your
view correspond to different
drag items, so you have to tell
And, in this case, I check if
the local object of any of the
other items already in the
session correspond to the
message that I want to check
now, and if it's already there,
I also abort, using an empty
So, this is how you get
Another behavior that Mail has
As I long-press on a thread,
instead of lifting one item that
represents the thread, one item,
I instead lift three that each
represent a message in this
thread, as indicated by the
little blue bubble, there.
As we've already seen,
item itemsForBeginning session
returns an array, so it stands
to reason we can, in fact,
return multiple items here, and
then that's something I'll do
So, I grab all the messages from
my mailThread, I sort them, and
I'll explain why in a second,
and then I return a UIDragItem
for each of them in the exact
same fashion that you've seen
Now, the reason that I'm sorting
them is that the order of the
array that you return here
matters, and it's so that the
last item in the array is going
to be the topmost object of your
lift, and since I want the
newest message first, I sort
them so that the oldest is the
first in the array.
And, this would work.
However, if this was all we
implemented, all the items would
lift with the same preview.
That means they would have the
same visual representation and,
by default, the preview that we
create is going to be based on
the view that the interaction is
installed on, so that would mean
that all three messages would
have the thread as their visual
And, since I don't want that, I
implement the optional
previewForLifting item session,
in which I get to return my own
So, what I do here is, I first
attempt to find the message
associated with that item, and
if I have it, I grab its
associated message views through
its helper method I happen to
have, and initialize my
UITargetedDragPreview with that.
And, that would mean that all of
the messages that I'm lifting
would have their own
representation based on the
message view dedicated to them.
And, last but not least, here's
another thing that Mail can do
that is kind of tricky.
You see me here dragging photos
from Photos to Mail, and if you
pay attention, you'll notice
that the photos lift into an
appropriate size for the entire
width of the compose sheet.
So, how is it that Mail already
knows how much room to make, and
where to target the preview?
Is it because the data just
happened to arrive so quickly?
No. You should not make any
In my demos, the data's going to
arrive in time, but that may not
be the case for your users, and
there's a better way to handle
It turns out that NSItemProvider
has a property called
allows you to communicate the
size that you expect something
to be represented at on the
other side, kind of out of
channel, so even though I'm
initializing the NSItemProvider
with the file here, I happen to
know the size, and I can set its
accordingly, and then Mail is
able to read that out on the
other side, and everything else
is just the same.
Now, you can take my word for it
that this works, but we've only
known each other for what, nine
So, Wenson's going to show you a
Thank you, Robb.
So now, before I jump into
the demo, there's a couple of
things I'd like to say.
All of the sample code that I'm
about to show you will all be
available online, and I strongly
encourage you all to check it
Second of all, we will be going
over not one but two demo apps
First one is called Drag Source,
and it will focus on drag
Second one, Drop Destination,
will focus, as you might have
guessed, on the drop side.
So, with that said, let's take a
look at our first demo app.
So, in here, we see four stacks
of images, and currently, we
have implemented very basic
cases of drag interactions, so
we're able to drag a single
image out of each stack.
So, that works great, but it
would be kind of cool if we
could drag an entire stack of
images out as individual items,
one per each image.
So, let's take a look at the
code and see what we can do.
Now, currently, if you look at
this, we just consider the last
image view, and use it to create
a new drag item, just using that
last image view.
Instead, it's a pretty short
stretch to enumerate through all
of our available image views and
return drag item for each one.
So, we're going to do just that,
and now let's see how it
So, watch what happens when I
begin a drag on the second stack
You'll notice that there's a
badge count of three this time.
That indicates that there are
three items in the drag,
corresponding to the three
images in the stack.
Now, if I bring up Photos on my
right side, here, you'll notice
that I can actually drop these
three images into Photos, and
it'll save them as individual
Now, while we're in Photos,
there's something else I'd like
to show you.
So, I've begun a drag on one of
these images, and now, if I tap
on these other two images,
you'll see that we add those to
the existing drag session as
So, as Robb mentioned, this is
not a behavior we get for free.
Luckily, it's pretty easy to
implement, and I'll show you
So, going back to the code, all
we've got to implement is
So, the thing to notice here is
that we can actually use the
exact same logic to construct
drag items in itemsForAddingTo
session as in itemsForBeginning
So, to make this easier for
ourselves, we'll just take the
logic that used to have in
itemsForBeginning session and
introduce a new helper method.
I'm going to call this dragItems
Then, in both places, when we
are adding to an existing
session, right here, and when we
are creating a new session in
itemsForBeginning session, we'll
simply turn around and call this
OK? So, that should give us the
ability to add more images into
our existing drag session, and
as you can see, as I tap on each
of these other three views,
we're able to add all 10 images
into the drag session.
So, that works pretty well, but
there's one caveat.
So, notice here, I'm going to
start a drag on the first stack
of images, and I'm just going to
keep on tapping the first stack
So, you can see that I'm able to
arbitrarily add a whole ton of
images that I probably shouldn't
be able to add.
This seems like a bug to me.
I have 26 copies of each image
now in the drag session.
So, let's figure out how we can
Now, here's our helper that we
just introduced, and what we can
do here is, instead of using all
the imageViews to create
dragItems every single time we
tap, what we're going to do is
filter out the imageViews, so we
don't use an imageView to create
more than one dragItem.
So, with those three lines of
code, I'm going to hop back into
the app and show you how it
OK. So now, I've begun a drag on
the first stack of images, and
I'm going to tap the next three
Now, watch what happens when I
try to add more items.
Our bug seems to be fixed.
We can no longer add redundant
items to our drag session.
So, these were just some basics
for manipulating the drag items
that we supplied through our
I'd like to now hand it back to
Robb to discuss some of the more
advanced techniques for
customizing animations and drag
So, let's add some polish to
One thing that's often the case
is that the view that you want
to lift is not quite ready for
prime time, so maybe there's
some highlighting state, or you
have some overlay that you want
to fade out.
And, the lift is actually a
great point to do that, because
during the lift, the view is
still live, so any changes you
make inside of that view will be
reflected during the animation,
and it's only that, at the point
where the user starts moving
their finger that we perform a
snapshot, and that state is what
the user will see for the rest
of the drag interaction.
The way you could animate
alongside the lift is like so.
There is an option delegate
session, in which you get handed
an animator object, and here I'm
going to just grab all the
messages that I have on the
items in the session, and find
their associated messageViews.
So now, I have an array of
messageViews, and for each of
them, I will just add an
animation to the animator in
which I fade out an overlay by
setting its alpha to zero.
And then, in the completion
block, I will set the alpha back
to one, and what that will do is
that as the view lifts up, the
overlay will fade out.
If the user lets go and the view
settles back into place, the
overlay will fade in, because
the animator is able to
automatically revert this
Then, when the drag starts, we
will snapshot the view, and
after that, the completion block
will get called, and the overlay
will be reinstalled.
So, that means in the snapshot,
there won't be an overlay, but
in the view that remains inside
the application, there will be.
But, what if the view that
you're lifting, the view that
you're lifting is not the view
that the interaction is
installed on, or what if the
view isn't square, or what else
can we do?
So, we already saw that
UIDragPreview can be initialized
with the view, but there are two
other parameters, and I'll go
over each of them individually.
The first is a parameters object
that allows you to customize the
appearance, and the second one
is a target that's used for
So first, the parameters.
That is an instance of
UIDragPreviewParameters, and it
has two properties.
The first is a color, and that's
going to be the background color
of the view that we will install
behind your view, because a lot
of views aren't actually fully
opaque, and it would look not so
good if we just lifted them as
However, you get to customize
this color in any way you want.
You can make it black, or clear.
You can really go to town, here.
The second property is a little
It's a UIBezierPath that lets us
know what the visible region of
your view should be, so if your
view is not square, you could
set a rounded rectangle here.
But, there are some things to be
So, by default, if you don't
supply drag preview parameters,
so you don't set a path on the
drag preview parameters you
supply, we will lift the entire
If you wanted to crop out the
subrect phon, so in this case,
the rounded rectangle with the
kid in it, you could supply a
Bezier path, and it would result
in something like this.
So, it's important, however,
that the Bezier path that you
supply has to make sense within
the coordinate space of the
So, in this case, the bounds
that I initialized this rounded
rectangle with have an origin
that is relative to the origin
of the container, as indicated
in gray, sorry, as, the origin
of the view, as indicated in
gray, so that's the top left
And, you want to also make note
of the midpoint, because it's
the midpoint of the visible path
that we'll later use for
positioning, when we talk about
So, this is how you would get
this kind of preview.
However, you're not limited to
giving us a path that is smaller
than the view.
You can also give us one that's
So, in this case, I chose an
origin that is negative, and it
works in the [inaudible] of the
view, and that would result in
this kind of platter that frames
And, the color you see here is
in fact a background color that
defaulted to white.
Now, if you're bold enough to
implement your own text
rendering, there's a dedicated,
thank you, style that you can
use to match the way that we
lift text, so you want to refer
to the documentation for that.
And, the target, so the target
is used to position a transitory
view that we will use to perform
the animation with inside your
If you don't supply your target,
we will infer one based on the
superview of the view that you
That means if you provide a view
as your view in the direct, in
the targeted direct preview, if
that view is not in the view
hierarchy, you will have to
supply your target.
Otherwise, we can infer one.
This UIDragPreviewTarget has
The first is the container.
This is where we're going to
install the view, so you want to
be aware of any add or remove
subview calls in that container.
And, the second one is a
position, and the third is a
The transform is only relevant
on drop, and it allows you to
rotate or scale on set down.
The position, however, is a
little more tricky.
So, as I mentioned, if you give
us a point in your container, as
indicated in gray, by default we
will center the midpoint of your
view around this position, so if
you don't supply a visible path,
it would look like so.
However, if you do supply a
visible path, then as I said,
the midpoint of the bounds of
this path will be centered
around this point.
So, it's no longer the midpoint
of the view.
It's the midpoint of the visible
And, it also means that if your
path is a little bit more
complicated, such as this one,
where I just unioned two rounded
rectangles together, the
midpoint is now not even in any
of the two rectangles.
It's still the midpoint of the
enclosing bounds of both of
But, if you already had a chance
to look at iOS 11, you have
noticed that a lot of the apps
in the system are actually able
to update the preview after the
lift, so here you can see Maps,
and as I move this little Apple
Park cell around, it gets
replaced by this little map
snippet after the fact.
So, how can we do that?
Well, it turns out, there's a
second preview class in the
systems, next to
UITargetedDragPreview, and that
is UIDragPreview which, as you
might have guessed, is very
UITargetedDragPreview, but it
doesn't have a target.
All the other semantics still
apply, and the view that you
initialize this preview with may
or may not be in the view
It's not relevant anymore, at
But, how will you update this
First, you want to find a spot
in your session lifecycle where
So, in this case, I chose
sessionDidMove, and what I want
to do here is that as the user
moves out of the listView in my
hypothetical Mail app, I want to
replace what they're dragging
with a little envelope graphic.
So first, I perform a hit check
to see if I'm still inside the
listView, and if I am, I just
abort by returning nothing.
And then, I iterate over all the
items that have a message as
I check if I have already
updated this item, because this
operation is not free, and
sessionDidMove may get called
quite frequently, but if I
haven't, then I will set the
previewProvider, and this is a
block that we will later call to
update the preview, and inside
the block, I first create an
imageView with the image I would
like, and then I initialize a
new drag preview with this, and
it's important to realize that
we may not actually call in this
So, if you are lifting many
messages, we may not, we may
decide not to display all of
them, and we wouldn't bother
calling in a preview block for
the views that we don't actually
show on the screen.
And, last but not least, I have
to do some bookkeeping.
So, theory is still second to
practice, and Wenson's going to
give you another demo, and I'll
see you in the last.
Thanks again, Robb.
So now, I'd like to introduce
the second example we are going
to be looking at in Drag Source.
So, check this out.
When I drag on this image of two
QR codes, we have a drag session
that contains two items.
What are these two items?
Well, if I drop it in Photos,
we'll see that it's actually the
cropped images of the QR codes.
So, I've gone ahead and
detected, where are the QR codes
are already in this image?
Now, the thing we can polish
here is the drag preview.
So, we haven't done any
And so, by default, we use the
entire image view to represent
either of the items, either of
the QR codes.
That is, we are actually seeing
the entire image view twice, two
of them stacked on top of each
It would be kind of cool if we
could use just the cropped image
of the QR code as the drag
preview as we are lifting, and
when we are dragging them
So, let's take a look at what we
need to do this.
First thing we're going to do is
item, so in here, we're going to
take some information about the
QR code, namely the cropped
image of the QR code, as well as
some geometry describing where
it is in the image, and we're
going to use it to create a new
Then, we're going to create a
drag preview target and drag
Note that right here we set the
visiblePath to a new
UIBezierPath that's a
roundedRect, and that will give
us a nice rounded preview.
So, we combine all of this
information into a new targeted
drag preview, and with this
change, we should see a much
more polished drag preview when
Now, check out what happens when
I begin to lift.
So, instead of the entire image
view popping up this time, we
see individual rects for the QR
codes get lifted up, and as I
drag, you can see that these are
the two QR codes flying around,
so that looks pretty good, but
there's something that looks
kind of weird, and I'm about to
Watch what happens to the QR
codes when I let go.
Now, I've let go somewhere that
doesn't actually accept the
drop, and so we'll do a cancel
The problem is that we haven't
actually told UIKit where the
drag preview should animate to
when we cancel.
So, let's fix that problem.
We're going to do that by
This looks and works a lot like
In fact, observe that if we want
the QR codes to go back to their
original locations, what we can
actually do, similar to what we
did in the first example, is
take our code that used to live
in previewForLifting item and
factor it out into a separate
So, we're going to call it
dragPreview for item, and what
this is going to do is return
the original location of the QR
codes in both the places where
we are lifting and when we are
So, I'm going to call that
helper in these two places
really fast, and rerun the
Alright. Now, let's see what
happens when I cancel.
And, see that they fly back to
their original locations and
then settle down.
So, that looks so much better
than it did before, but there's
one more thing I'd like to show
So, we're going to hop on over
to the right side, where we have
Photos, and you can notice that,
as I drag some images, we'll
fade out the background of the
image views to kind of indicate
that we are currently dragging
an image from that view.
There are a lot of apps around
the system that do this, and we
can certainly get the same
effect in our own demo app.
So, I'll show you how.
What we're going to do is
implement a few alongside
So, as we animate the lift, we
get this animator object that
we're able to attach alongside
So, we're going to add this new
block that sets our alpha to
0.5, our alpha being the alpha
of the overall image view.
So, we're going to fade out the
image view as the lift is
happening, and as the lift is
canceling, I'm sorry, as the
drag preview is canceling, we
are going to revert the alpha to
1, so we're going to fade the
view back in.
Now, it would be kind of a shame
if our alpha were permanently
ghosted at 0.5, so when the drag
session ends, we've got to be
careful and set our alpha back
to 1, to make sure that we're at
full opacity when the drag
So, with those changes, I've
rerun the app, and now watch
what happens when I begin the
You can see, this nicely
indicates exactly where the QR
codes are by fading out the rest
of the image view, and as I
cancel, you notice that the rest
of the image view fades back in
just as nicely.
So, that's all good and
Let's look at our third example.
This is Draggable Location Image
View, and in here, the trick is
that we're adding not only the
image as a representation to the
item providers when we start a
drag, but we are also adding the
What that means is that I'm able
to drop into an application that
accepts location, such as Maps,
at it will actually navigate me
and drop a pin at the location
where this photo was taken,
which is, of course, the Golden
So, that looks pretty good,
except for one thing.
Now, when I begin a drag, I
haven't done any customization
around the drag preview, and so
this default drag preview, which
is the entire image view,
doesn't do a really good job of
really highlighting the fact
that we have a location, and not
just an image.
It looks just like we're
dragging an image right now.
So, let's fix that.
Now, we're going to go into
Draggable Location Image View.
This is where our logic is going
to live, and we're going to
So, what do we want to do when
the session is about to begin?
We're going to take our drag
item that we've created, and
we're going to set the
previewProvider property to a
Now, in this block, what we're
going to do is create a new
This is just a custom view I
wrote that knows how to
represent both an image, as well
as some text describing the
location of the image.
And, we're going to create a new
UIDragPreview using this
OK. So, with that change, we
should be able to see a much
nicer, hotter representation for
our drag preview.
So, watch what happens when I
begin a lift.
Now, interestingly, there's
actually no difference.
The reason is because we put our
logic into sessionWillBegin, and
the session does not begin until
I actually start moving my
So now, I'm going to start
moving, and look at that.
The drag preview has now morphed
into this platter representation
that shows both the image.
That now shows both the image,
as well as the location, and as
always, I'm able to drop into
It'll navigate me and drop a pin
So, we've discussed a number of
the advanced techniques on the
drag interaction side of things.
I would like to now hand it to
my other colleague, Tom, to
discuss some of the advanced
APIs used to customize drop
Thanks, Wenson, for finally
dragging me into this.
No, I'm happy to be here.
So, let's talk about the drop
Let's take a deep dive into a
We're going to talk about drop
sessions first, and that will
bring us to actually performing
So, what is a drop session?
It's the other side of a drag
It gives you access to
everything related to a drop.
You can get access to the drag
location where the user is
dragging inside your view.
It gives you access to the items
in the view, what type of data
is there, and actually their
data in the end.
It gives you access to the
configuration, so you can act
And finally, it gives you access
to the drag session itself when
you're dragging locally, but
more about that later.
One thing to keep in mind about
drop interactions is that only
one interaction would handle
only one active drop session at
the same time.
Why is this?
Because, it would fit most use
That means that once the user is
dragging around and enters your
interaction, any other session
that comes around and tries to
enter your interaction won't be
Remember, you can drag with more
than one finger at the same
Now, if you don't want this
behavior, and you do want more
than one session to be active on
your view at the same time,
there's a few options.
You can add more interactions,
just add a few more drop
interactions, and they will all
be handled at the same time.
They can have the same logic, or
they can have different logic.
Doesn't really matter.
Or, there's a property you can
set on the interaction called
Set it to true, and that will
lift the block on only one
session, and you can handle more
than one session at the same
But, your delegate has to handle
Let's talk a bit about how a
We've been over this yesterday
in the introductory talk, but
let's gloss over it.
User is dragging something, it's
approaching your view.
Before we do anything, we'll
call canHandle session on your
interaction delegate, and that
will trigger, depending on what
you return here, will allow you
to handle the session or not.
If you return false, it will be
as if the view doesn't appear to
the drop session, and nothing
If you return true or do not
implement this, will continue
and call sessionDidEnter to
indicate that the drag has
entered your view.
User then moves their finger
around, and will call
sessionDidUpdate repeatedly, and
you have to return a drop
Now, keep in mind, this will be
called a lot of times, so try to
do at least minimal work here.
Don't do too much, because your
frame rate will suffer and users
don't like that.
When a user lifts their finger,
will execute a drop.
More about that later.
And finally, will call
sessionDidEnd to indicate that
the session has ended and your
interaction, by default, is
ready to accept new sessions
Now, let's pretend that the user
did not lift their finger, and
bring them back.
If they move outside, we'll call
sessionDidExit to indicate that
the session has left your view.
It does not mean that the
session has ended.
It's still going on, so if the
user lifts their finger outside
of your view, we'll call
sessionDidEnd again to indicate
that the session has actually
Now, let's pretend again that
the user did not lift their
finger, and bring it back,
outside of the view, they bring
it back in again, we'll call
sessionDidEnter again, and start
calling sessionDidUpdate to
update the session and get a
Now, pretend that the user has
rested on a place inside your
view where you cannot accept a
You'll return an operation
cancel or forbidden.
If the user then lifts their
finger, we'll call sessionDidEnd
No drop is executed, and we're
just canceling the drop.
Let's focus on this drop
proposal for a second.
I'm not going to talk about the
drop operations that was covered
yesterday in the introductory
talk, but there's two more
properties that might be
First, precision mode.
If you set precision mode by
setting isPrecise to true, your
will hit tests inside your view
slightly above the touch of the
So, the actual hit test location
inside your view will be not
under the finger, but slightly
This allows more precise
dropping inside your view,
because the user can actually
see where they are dropping.
A good example is the Text
They use precision mode to show
with carets where the user will
actually drop the items inside a
You can see it here, that the
caret is shown slightly above
the touch where the user is
touching the glass.
If you would not do this, the
caret will be below the finger,
and it will be very hard to
precisely drop inside a specific
point in the text.
So, if you do implement
precision mode, please indicate
some UI at the drop site to
indicate to the user where they
will be dropping this items.
Next up is
This brings us to preview
As you might have noticed by
playing around with iOS 11, if
you start to drag something, it
will scale it down.
The system will always scale
Why do we do this?
Because it doesn't make a lot of
sense to have a big preview
covering the screen and your UI,
because it's interactive.
If you blocked the screen with a
preview that's too large, it's
hard to navigate around, so we
scale those down, but in certain
cases, it might be interesting
to prefer a full-size preview.
For example, you have a list and
this list you can reorder.
So, you pick something up and
try to drag it up.
It would not make sense to scale
that whole item down, so you can
There's two ways to do this.
At the drag site, there's drag
Return true here, and we'll try
to keep those previews full
size, and at the drop site, you
can set the flag to true on the
Note that this is a preference.
You can ask to scale, not to
scale, but we might not always
There's certain conditions where
the system will scale down
A few of these are flocks.
So, if you add more items to the
drag, we will always scale those
items down, even if you prefer
A single preview, if you are
dragging one item and dragging
it outside your app, we will
always scale that down, too.
And finally, once something is
scaled down, we will never scale
it back up again.
So, keep that in mind.
It's a preference, but not
something set in stone.
Let's go to performing a drop.
When you're ready to perform a
drop, user lift their finger,
and we'll have to start loading
the data at this point.
In fact, this is the only moment
in time where you can actually
request data and allow it to
succeed, because in any other of
the lifetime calls, if you try
this, it will always fail.
Only in performDrop you have a
chance of getting data.
There's cooperation required on
the other side, so that's why I
say, "There's only a chance,"
but usually you will get some
These data loads are always
asynchronous, so please don't
If you block for too long and
you don't know how long this
data will be taking to arrive
there, will kill your app, and
that's not the best user
experience for our users.
So, don't do this.
Load data in the background will
animate the items down into your
view so the user can see you
dropped, and then finally, we'll
call concludeDrop to indicate
that the animation is done, and
as far as the user is concerned,
the drop is finished.
This does not mean that the data
is there yet.
If you can see, the first call
here is still going on.
But, more about that later.
How do you load data?
There's a very useful call on
the session called loadObjects
of class completion.
It's very good to load
If you have only images in the
drag or in the drop, and you
know you can only accept those,
use URImage as class here, and
it will give you back a nice
array sorted exactly the same as
the sessions, in the sessions
items array, and we'll give it
right to you.
We'll do the heaving lifting
behind your back, and you'll get
a nice array back.
This completion block will be
called on the main queue, so you
can update URI right away.
If you have more mixed data
here, or you wanted some more
control, you can just iterate
over the session items and load
each of them individually, if
Use loadObject, or
loadFileRepresentation on the
It gives you more fine-grained
control over what you want to
load and how, and it even allows
you to load multiple file
representations for each item,
if you choose to.
Keep in mind that this
completion block will be called
on the background queue, so if
you want to do URI work here,
dispatch to the main queue.
I'm going to hand it over back
to Wenson to show off how that
actually works in practice.
So now, I'd like to introduce
the second part of our demo.
This is the, this is the second
demo app, called Drop
Destination, and what we'll be
doing here is building a photo
gallery very similar to the
Photos app, where dropping
images will populate this area
with additional Image views.
So, the idea is that this flow
I should be able to drop here,
and I should see more Image
Now, of course, that didn't
happen, so let's go into the
code and see why that's the
So, this is where most of our
logic is going to live,
Droppable Image Preview
Controller, and here, you can
see that all we've implemented
So, it's no wonder that the drop
doesn't work, because we haven't
actually implemented any drop
I'm going to implement
performDrop right here, and in
this method, we are going to
iterate through all of our items
in the session.
Now, for each item, if we are
able to load a UI image, we're
going to go ahead and insert a
new Image view into our
hierarchy and kick off a load
from the itemProvider.
Now, when the itemProvider is
done loading, we're going to
call back to the main queue and
set the image of the Image view
that we just inserted to this
new image returned by the
So, with that little change, we
should be able to get this flow,
this basic flow to work.
So, let's see what happens.
Now, the first thing you'll
notice is that now there's a
green plus-three badge.
This indicates that there is
indeed an action to be
performed, and that action, of
course, is inserting new images.
So, that works.
It's very basic, though.
There is now another feature I'd
like to highlight while we're
So, you might have noticed this
area at the bottom that says
Drop here to delete photos.
It does what it says on the tin.
When I drop it here, we remove
it from the top area.
So, that's kind of nice, but the
thing is, we haven't done any
customization around the drop
preview yet, and so by default,
images just kind of fly towards
the center and fade out.
I'm going to now hand it back to
Tom to see what we can do to
make this better.
Turns out, you don't need a
lot of codes to perform a drop.
So, let's talk about drop
previews and their animations.
Let's bring back this diagram,
but it turns out that it's a bit
more of a simplification, and
there's more going on.
So, let's bring this
concludeDrop to the side, and
let's talk about what's going on
Started loading our data, and
once we've performed our
completes, we ask you for a
preview for dropping the item by
calling previewForDropping item
with defaultPreview, giving you
a default preview.
You can return a new preview
here, or return the default
preview, or nil, whatever you
More about that later.
So, any of the previews we get,
or the defaults we have, we'll
use these and animate those down
into your view, so the user can
actually see something dropping.
While that's going on, we'll
animate willDropWith animator so
you can animate alongside.
Now, as Robb mentioned before,
same as in the lift side, the
drop side is live, too.
While you're dragging, there is
a snapshot, but while you're
dropping or canceling or
lifting, the view is live, so
you can also update the view you
give us here, or animate
alongside any other UI you have.
Those animations finish, and
we'll call concludeDrop to
indicate to you that the drop is
finished and, as far as the user
is concerned, they can continue
with their business.
Now, again, this does not mean
that the data is already there.
You can see here, and that's
just two examples, there's one
very long load object call
that's going on beyond
There's one that, like, ends in
the middle between willAnimate
and previewForDropping, and even
the previews are animating at a
That's because, depending on
what target you give us, they
might take longer to travel
Something that's farther away
from the finger will take a
little bit longer than something
that's closer to your finger.
So, keep this in mind.
The animations do not take the
They are slightly different.
So, we have drop previews, and
as Wenson already showed, we
also have cancel previews.
They look almost exactly the
same, and the same goes for
It's the same approach, but just
Wenson's previews demos showed
that it's probably better to
item, because it gives a better
You can fly back the items.
When you update the UI, you can,
I mean, the user can navigate
around so your original UI can
be very different than the one
that you started at, so keep
this in mind.
willAnimateDropWith animator is
very similar to
And again, different occasions,
but the same approach.
The UIDragAnimating protocol
here is very similar to
you'll be right at home, there.
Now, let's talk about this
default preview we give you.
Why do we give it to you?
You could just return it here,
and you get this.
Well, that's fine, but that's
not why we give it to you.
So, if you do want the default
preview, and you want the
default animations, just return
That indicates to the system
that you're fine with the
defaults and the system can do
what it wants to animate
everything down, and how it
So, why do we give you this
Well, you can retarget it.
That's why we, what we want you
If you retarget it, you know
where it's going to be inside
We'll animate it down into the
target you specified, and that's
a better experience.
That only works, of course, if
you know where to target to.
If you don't know the location,
you can't retarget.
And finally, you can create your
own custom preview and make your
own UI here.
You're free to do what you want.
The preview you give us will
animate to the target you
There's a few limits here.
If there's fewer items in the
flock, then we'll ask you a
preview for each of the items
and give you an alongside
animation for each of the items,
so depending on how much they
are, you'll get these.
If there are many items in the
flock, or in the session, then
we'll use the default previews
for all of them, so we won't ask
you for preview.
We do give you one alongside
animation to go with that one
animation for all the items.
Don't take my word for it.
Wenson's going to show how to do
Thanks again, Tom.
So, to jog your memory, the part
that we'd like to polish is this
Let's find out how to do that.
So, we're now in Droppable
Delete View, and over here,
first thing we're going to do is
implement a previewForDropping.
So, given the item, we're going
to create this drag preview
Now, this looks very similar to
what we've done before, only
this time, we have an explicit
transform set, so what this is
going to do is animate our
default preview's width and
height to 10% of its original
So, you're going to specify that
We're also going to set the
center to the be the iconView
The iconView is, if you go back
to the app, this little trash
can at the very bottom, here, on
So, we're going to animate to
there, and we're going to
retarget the default preview to
that location using this target.
But wait, there's more.
We can actually do a little more
Let's add an alongside animation
on the drop, as well.
So, let's add a transform to the
iconView to the trash can as the
drop is taking place.
So, we're going to transform it
to 1.25 scale.
That's going to make it grow
slightly, by 25%.
Now, we don't want to have it
permanently at 125% size, so
we're going to set the transform
back to the identity when we
conclude the drop.
So, with these little tweaks,
should be able to see a little
more polished experience.
I'm going to go back and drag
some photos here.
So, pay attention to what
happens to the photo when I drop
You see, this time it goes into
the trash can and disappears.
And, speaking of the trash can,
you also see that kind of grow
in size and then shrink when the
drop is concluded.
So, that looks a lot better than
it did before.
So, I'd like to now show you
something that might not look as
So, in this case, this is the
last panel of Drag Source.
We have Slow Draggable Image
Now, they're called slow because
they're stimulating items coming
in from a remote server far, far
If I drop these four photos into
here, it's going to take a
really long time to load.
So long, in fact, that we will
begin showing this app modal
dialog that at least allows the
user to cancel.
But, as is the theme of this
presentation, this can also be
So, I'm going to hand it back to
Tom to see how we can do that.
So, how do we deal with slow
Like I mentioned before, data
loads are always asynchronous.
So, there's two disconnected
timelines at play here.
There's data loading, one goal,
and there's animating the drop
previews, and they're not the
Bring back this diagram, you can
clearly see that there's not one
line here that's equally in
The loadObject calls take
different time, and the preview
animations take different time.
And, you can also see that we
don't have data yet at the
moment we ask you for a preview.
One use case for this, or one
case that you might run into if
you drag photos from Photos into
an email, and those photos might
be stored on iCloud, because
we're saving space on your
While you're dropping, well show
the app modal UI, giving the
user some sense of progress and
a way to cancel out.
That's a real-life use case.
So, you saw this Cancel button,
because we don't want to user to
be waiting forever.
We don't know how long the data
will take to arrive.
Might be two seconds, might be
So, we give the user a way to
If that happens, we'll call the
completion blocks with nil data
and an error set so you can
Additionally, both sessions and
item providers provide
The session is
ProgressReporting, so you can
observe its progress.
And, the item provider load
methods all return a progress
object you can also use.
Progress has a cancellation
handler which is a perfect spot
to handle the cancel.
Add you code there to handle any
of the items coming in and not
being there, and you can remove
them again from your modal.
Now, this also brings us to
showing custom progress, like
Wenson said before.
If you don't want this app modal
UI, you can turn it off by
progressIndicatorStyle to none,
and then we won't show the UI at
Now, this does mean you have to
provide that experience to the
You can do this by observing the
There's a progress on the
session, and the per-item
progress returned by the item
If you do this, use this
progress to indicate some UI
there where the user is, where
you can see, for each item, if
it's loading or, in general, for
your view, but please allow the
user to cancel or navigate away,
so they are not blocked on using
But, the big question remains,
how do I generate a preview if I
don't have any data?
I want to create this custom
preview, but it doesn't work.
Well, turns out, you can't.
If there's no data, you cannot
create a new one.
Just use the default previews.
They're a pretty accurate
representation of what's
actually in the item.
Was set by the drag side, and so
you can actually use this to
animate this down.
We target it, add a transform,
you can change however you want
You can also make a placeholder
If you have something that you
want to show, and like, show a
spinner there, that's probably a
good idea, if it makes sense for
One of the great things is that
Collection view and Table view
have built-in support for this,
so you don't have to worry about
It's very easy to turn on.
So, I know it's early, tomorrow
at 9 a.m., fourth day of WWDC,
but please come to this session.
It will be worth your time.
So, never assume the data will
That's the one advice I can give
Even if you are testing, and
locally it might be, the data
might be there right away, this
might not be the case for your
You don't know how it's going to
be in the field.
And, always account for the
That's the best approach you can
take, even if it goes fast, does
good, but assume that it's going
to take a while.
If implemented properly, this
could look like this.
Custom preview, custom progress
here, so your user is not
Finally, let's talk about how to
improve your in-app experience
by adding drag and drop.
Drag and drop is something you
can use, to use between apps,
but you can also use it to
enhance your own app.
There's a few nice things we
added to accommodate that.
First is localDragSession on
This gives you access to the
drag session, as I mentioned
You can access any kind of data
in the drag session.
The items again, any stages that
are, is available.
It only works for in-drag apps.
If you're dragging outside your
app, the drop session will not
have corresponding local drag
Additionally, as Robb showed
before, there's localObject on
It's a very good container for
You can use it to have states,
set some states in
itemsForBeginning session, and
use that state to generate a
lift preview, for example.
Or, you can use it to transfer
data from the drag site to the
That's much easier than building
itemProviders that will transfer
your data outside your app.
If you do allow the drag to go
outside your app, you still have
to do that, of course.
And finally, there's
localContext for UIDragSession
which allows you to keep states
for that drag session and the
drop session, of course,
locally, without resorting to
app global states, and it makes
things a bit easier for you.
How do you keep a drag inside
There's a method you have to
implement on the dragInteraction
If you return true here, then
the drag won't be able to leave
The user will still be able to
drag outside your app, of
course, but any of the drop
sessions outside your app will
not see the drag.
So, visually, it will look the
same, but nothing will be able
to accept it, only inside your
You can also inspect this on the
drag and the drop session, if
you want to.
One last thing, local drag and
drop for iPhone, this is
disabled by default.
On iPad, it's enabled by
This is because you want your
apps to behave according to size
class so that if you have a side
app, that works, too, but still,
on the iPad, you can drag out
into another app, but an app of
the same size will not work on
So, if you do go on to enable
drag and drop inside your app
also on the phone, you have to
enable it by setting isEnabled
on the drag interaction to true.
That will enable the
interaction, even on the phone.
I'm going to hand it over to
Wenson for a final demo.
Thanks again, Tom.
So now, we're going to put
together everything we've
learned so far to implement our
own custom progress UI.
Let's just jump right into the
So, we're going back and
revisiting Droppable Image Grid
Now, here's the function that we
implemented earlier for
We're just going to add a few
It's going to look something
First of all, we're going to set
the progressIndicatorStyle to
This is going to instruct UIKit
to not show the app modal
Next, we're going to remember a
little bit of state for the drag
item that is being dropped.
Now, the important thing here is
that we're going to remember the
view that we are inserting into
the view hierarchy of the grid,
as well as this progress that's
returned when we load object on
the item provider.
This is going to come in handy
right now when we implement
So, the first thing we do here
is we're going to read some
state out of our item states
This is going to describe
information about the item that
is being dropped.
Namely, it will allow us to
create a new progress spinner
This is a custom view that just
knows how to represent a spinner
indicating the progress of a
So, we'll create this custom
view right here.
The rest is very similar to what
you've seen before.
We'll create a target, create a
drag preview using that target
So, consider this.
What happens if the image
actually loads really fast?
What will happen is, we'll show
something at their destination
while the drop is still
animating, so we'll see this
drop preview flying to the
destination that already has
content, and the drop preview is
going to show this spinny
It's going to look kind of
So, this will handle that edge
case right here.
What we're going to do is set
the alpha of our destination
view to zero, so we're going to
hide whatever we show at our
destination while the drop is
Now, when the drop is finished
animating, we're going to set
the alpha back to one, and
what's going to happen is that
the drop preview at the
destination will fade away and
give way to show the actual
destination view underneath,
because we set the alpha to one
So, that should take care of
There's one last bit of
bookkeeping we should do, and
Now, when the drop is finished,
we've just got to do a little
bit of good bookkeeping and
remove all of the items that are
no longer relevant in our
dictionary of item states.
So, that was a lot of, those
were a lot of changes.
Let's see it in action.
So, I'm going to repeat the same
scenario with Slow Draggable
Watch what happens.
This time, we get a different
progress UI for each drop that
is happening, each item that is
being dropped, and that is
really cool, because it allows
us to do things such as this.
If I repeat the same procedure,
you can see that I'm able to do
things like scroll the Image
view, sorry, the Grid view, and
also interact with different
items while the load is
So, that's some really powerful
stuff, and we've come a long way
in today's session.
I would like to now hand it back
to Tom to give a quick recap.
I'll see you at the labs.
That looks pretty sweet, if
you ask me.
Anyway, we talked about how drag
and drop can be a very powerful
and user-driven input-output
mechanism for your app.
You can create custom and very
stunning visuals on the lift
side, on the drop side, and when
You can animate a lot.
We talked about how to handle
asynchronous data, and
And finally, we mentioned how
you can use drag and drop even
inside your app, to make your
app a lot better.
There is some more information
You can re-watch this video, if
you weren't here.
That would be strange, I guess.
There's a few related sessions,
if you missed Introducing Drag
and Drop yesterday, please watch
the video, it's chock full of
Again, there's two sessions
tomorrow, on Collection view and
Table view, and then data
delivery, back to back, in Hall
Thanks for listening.
Enjoy the rest of your WWDC, and
see you around.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.