WWDC2017 Session 223

Transcript

[ Applause ]
>> Good morning.
Welcome to Drag and Drop with
collection and table view.
I'm Tyler Fox.
I'll later be joined by my
colleague Mohammed Jisrawi.
And unfortunately our
co-presenter Steve Breen had a
last minute medical emergency
earlier this week, so he's been
an amazing trooper and has been
recovering quickly but
unfortunately can't join us on
stage today.
So just a quick round of
applause, because he contributed
a lot to the stuff we're going
to talk about today.
So with that, let's talk about
Drag and Drop in collection and
table views.
We built a nice little app to
showcase some of the ways that
you can use Drag and Drop in
collection and table view.
In this app, it's a nice photo
library where we have albums and
photos in each of those albums,
and you can drag photos around
from one album to another.
You can even actually drop them
directly into that table view of
each of the albums.
And you can reorder photos
within the actual collection
views.
So we're going to show you how
you can actually use our new
APIs and iOS 11 to add Drag and
Drop to collection and table
views in all of your apps.
Now, you've probably been to
some of the sessions earlier
this week about all of this new
great API that we have in UIKit
for Drag and Drop.
So we have this great new drag
interaction and drop interaction
API layer.
We have new enhancements to NS
item provider in iOS 11, but
today, we're going to focus on
UICollectionView and
UITtableView, of course.
And we have new higher level
APIs specific for those classes.
So you might be wondering why do
we need new APIs just for
collection and table view?
It's a great question.
We wanted to build some specific
APIs that are focused around
cells and index paths.
As you know, collection and
table view are composite views
that build up a lot of smaller
cells to display on screen.
And so we realized we can help
you out a lot by speaking in
those native cell and index path
terms that you're familiar with.
We also wanted to make it easy
to build fluid animations and
get consistent behavior with
your collection and table views
across the system.
And we also recognized there are
some specific challenges with
collection and table view to
handle a synchronous data
loading.
We have some really cool new
stuff that we're really excited
to share with you later in the
session.
Finally I'd like to make a note
that throughout this talk we're
going to be showing examples of
collection view and table view
code but you should know that we
have a consistent API for both
of these classes, so sometimes
we're going to show one or the
other and you should know that
[inaudible] some small naming
differences, everything applies
equally to both.
We'll call out the differences
when there are some.
With that, let's go through our
agenda for today.
We're going to start by covering
the basics.
How you can very quickly just
get Drag and Drop up and running
in collection and table views.
And then we're going to focus on
perfecting drops.
With just a little bit more
work, you can make your Drag and
Drop experience in collection
and table view just look
awesome.
And we'll show you some really
cool animations you can add and
a whole bunch of great stuff.
And finally we'll talk about
final touches, some really nice
customizations and additional
things that you can take
advantage of to make that
experience really shine.
To start, I'd like to bring up
Mohammed to tell you all about
the basics of getting Drag and
Drop in your collection and
table views.
Mohammed.
[ Applause ]
>> Thanks Tyler.
We wanted to make adopting Drag
and Drop in your apps with
collection and table view as
easy as possible.
So to help you do that we're
introducing two new delegates on
both collection and table view.
The first is the dragDelegate,
which has methods for initiating
and customizing drags.
And second is the dropDelegate,
which has methods for completing
drags.
So basically data transfer,
customizing drop animations,
things like that.
It's important to note that
these two protocols are
completely independent.
You can use one or the other, if
you want one-half of the
experience or you can use both
to have both Drag and Drop.
Using both also unlocks
additional functionality like
reordering within your
collection or table view.
Implementing drags is pretty
straightforward.
You conform to dragDelegate and
implement one required method.
Items for beginning session at
index paths is called when the
user initiates a drag out of
your collection view, so
basically when they start
dragging a cell.
It's the app's responsibility to
use the input session and index
path to figure out how to handle
that drag.
So returning an array of
UIDragItems initiates the drag,
whereas returning an empty array
causes the drag to be ignored.
You might remember that tapping
other items, or other views, in
your app to add them to a drag
that's already in progress is a
key experience of Drag and Drop
on iOS.
Opting into this behavior in
collection and table view is
really easy.
There's an optional method on
dragDelegate that's called
whenever you tap a cell in the
collection view while a drag
session's already in progress.
Items for adding to session at
index path point gives you the
opportunity to return drag items
to add to your in-progress drag.
Returning an empty array from
this method causes the tap to be
handled normally.
So it could be interpreted as a
cell selection, for example.
Accepting a drop is equally
easy.
Similar to the dragDelegate,
there's a single required method
to get you started.
Perform drop with coordinator
gets called when the user lifts
their finger over your
collection view releasing the
dragged items.
This method gives you the
opportunity to handle the drop
using the drop coordinator.
The drop coordinator provides a
lot of convenience methods for
you for doing things like
accessing the dropped items so
you can actually perform the
data transfer.
It gives you some really great
convenience methods for updating
your collection and table view.
And finally it gives you some
ways to specify some default
system animations for your drop.
So now that we've talked about
how easy it is to adopt Drag and
Drop in your collection table
views, let's take a look at it
with a demo.
So I'm going to start out with a
basic gallery application that
uses a collection view to
display a grid of photos.
And we're going to add the
ability to drag images out of
the collection view and to
import images back in by
dropping them from other apps.
So, as you see, I've already
started with kind of a stub
implementation of
itemsForBeginning Session at
IndexPath.
And I'm just going to add some
basic code that goes to our data
source, retrieves a photo model
object, and then pulls out a UI
image from this photo using our
image property.
Since UI image already conforms
to NSItemProvider writing, we
can just use it to create an
NSItemProvider.
For your own model object, if
you're dragging something other
than images, you'll have to make
sure that those objects conform
to NSItemProvider writing.
There's a great session about
that just after this.
Once we have the NSItemProvider,
we can use it to create a
UIDragItem which will return to
the system to start the drag.
So I'm good to go to start
dragging things, but I also want
to add the ability to flock
things to my in progress drag,
so I'm going to add an
implementation of items for
adding to session at IndexPath,
point.
And I'm just going to do the
same thing that I did in my
itemsForBeginning Session method
to just return a bunch of drag
items.
So let's build this real quick
and see what it looks like.
So I'm going to pull up the
photos app and pin it next to my
gallery app.
You'll see that I can now start
dragging things out of my
collection view, and, if I tap
some of these other cells, they
flock over to my in-progress
drag, and I can carry them over
to photos and let go to drop.
So now that we have dragging
items, let's look at adding --
accepting drops.
So, as we discussed earlier,
we'll have to implement the one
required method on collection
view dropDelegate,
performDropWith Coordinator.
Here we're going to take
advantage of the coordinator's
convenience methods to get some
information about the drop and
then actually perform it.
The first thing we'll want to do
is ask the coordinator for the
destination index path.
This IndexPath is derived from
the point at which the user
releases the dragged items.
Since that point could be over a
region of the collection or
table view that's completely
empty, it could be nil.
So we'll just handle that case
here by returning a zero zero
and inserting it at the --
inserting it at the beginning of
our collection view.
Next, we're going to take
advantage of a convenience
method on the drop session to
load all objects of a specific
class in the scenario UI image.
That's what we're interested in.
And then in load objects'
completion closure we'll update
our collection view, like we
normally would, by calling
perform match updates with some
data source updates using a
convenience method that we've
written in the past.
And we'll ask the collection
view to reload the first
section.
So let's give that a quick try.
So now I'm going to pick up a
couple of these photos from the
photo's app and I'm going to
just drop them over my
collection view to accept the
drop.
So I can pick up some items from
photos actually this time.
Go back over to my app and let
go to accept the drop.
[ Applause ]
Thank you.
So now that we've actually seen
what it looks like [laughter],
just by adding a couple of
methods, it was easy to get
started.
Now Tyler is going to talk about
how we can build some really
awesome drop animations.
>> Thank you.
[ Applause ]
Thanks Mohammed.
All right.
So let's turn our attention to
really focus in now on
perfecting these drops.
And I'll start by talking about
something called a drop
proposal.
So if you've been to some of the
earlier sessions on the other
APIs that are available in
UIKit, this might be familiar.
But a drop proposal is your way
to communicate to the system how
you'd like to handle the drop
session before the user actually
releases their touch to perform
the drop.
The system is going to ask you
to create and return a drop
proposal multiple times while
the user is dragging over the
collection or table view.
Now collection and table view,
each have subclasses of
UIDropProposal, and, as a
result, that means they each
have that same property that
comes from UIDropProposal, which
is an operation.
So this is a value like copy,
move, cancel, or forbidden, and
generally indicates whether or
not you're interested in this
particular drop session and how
you intend to handle the actual
data inside of that drop
session.
But collection and table view
drop proposals don't stop there.
They also have an additional
value, which is a drop intent.
What is this?
This is some additional
information that your app can
provide to the collection or
table view which will let the
collection or table view
actually update their appearance
during the drop session to
reflect what you intend to do.
So let's look at some of the
actual values for this drop
intent to understand it better.
We will start with the default
one, which is the drop intent
unspecified.
With this drop intent,
collection or table view won't
actually change their appearance
at all during the drop session.
As you can see here, when the
user's dragging around over it,
there's just a nice little badge
next to the drag preview and
that's it.
You might choose this drop
intent if you don't yet know,
for example, where the items
will end up when the user
actually realizes their finger
to perform the drop.
Or perhaps the drag session
contains a mix of items, where
they're going to end up in
different locations when the
actual drop occurs.
So that's the first drop intent.
Unspecified.
The second drop intent is
insertAtDestinationIndexPath.
With this drop intent, it means
that you intend to insert a new
item or row into the collection
or table view at the destination
index path.
And, as you can see, collection
and table view will actually
open up a gap at this location
to let the user know that's the
location where their drop will
be accepted.
The third drop intent is
insertIntoDestinationIndexPath.
This is something that you want
to use when the user is dragging
over a cell that represents some
sort of container of items.
For example, here we have photo
albums, containers of photos, or
maybe you have a folder cell or
a list cell.
In this case, you intend to
insert the dropped items inside
of that container.
And so, table view, for example,
is going to highlight the row at
the destination index path so
the user knows that the drop
will be accepted inside that
row.
Now there's one more drop intent
value which is specific to
UITableView and that's the
automatic drop intent.
This is something that you
should use instead of
insertIntoDestinationIndexPath.
And this drop intent means that
you can either drop into an
existing container row at that
index path or you could move the
container row down out of the
way and insert a new row into
the table view at the same index
path.
And table view is going to
automatically tell you, when the
drop occurs, which one it is.
It'll pick between
insertAtDestinationIndexPath and
insertIntoDestinationIndexPath
for you.
So let's look at an example of
how you can provide a drop
proposal in code.
It starts by implementing this
optional method,
dropSessionDidUpdate,
withDestinationIndexPath.
Here this will be called
whenever the table view or
collection view detects that
there's a drop session moving
over it.
Now this method is optional, but
we strongly recommend that you
implement it because this is how
you provide your drop proposal.
Now because this is called all
the time when the collection or
table view sees a drop session
over it and whenever that drop
session moves, it's going to be
called very frequently.
And so your implementation needs
to be very efficient and return
quickly to avoid hanging the
main thread.
Now I'd like to call your
attention to this
destinationIndexPath that's
passed in.
Once again, this is our proposal
of where we think that you would
like to insert any items into
the collection or table view.
Now this destinationIndexPath
will be nil in some cases.
In particular, when the user's
dragging over a region of the
collection or table view where
there are no cells.
And another very important point
about this that might trip you
up, so be careful.
When the user drags over the end
of an existing section in the
collection or table view.
This index path might be equal
to the count of the number of
items in that section, which
means it may not correspond to
an existing item in that
section.
So be careful because that's an
insert at the very end of the
section.
So let's look at a quick example
of how you could implement this.
In this example, we'll look at
the local drag session property
on the drop session to see if
the drag originated from our app
or not.
If it's from our app, we can use
a drop operation of move and
otherwise we'll use a drop
operation of copy.
Both cases here we're going to
insert a new item into the
collection view, so we'll use
insertAtDestinationIndexPath as
our intent.
Okay. So that's drop proposals
which you provide while the
user's still dragging over the
collection or table view.
Let's switch gears now to talk
about dropping when the user
actually releases their touch
and you need to perform the
drop.
To set up some great animations,
you can use the drop coordinator
that we pass in to perform drop
with coordinator.
Collection and table view will
provide some basic animations by
default if you do nothing.
But for the best effects, you
can use the drop coordinator to
set up specific animations for
each item independently in the
drop session.
Let's look at some of the
methods that we've made
available for you to do that.
The first one is dropping to an
item or row.
This is a drop animation that
you'll use with the intent
insertAtDestinationIndexPath.
And you want to use this when
you've already performed an
update to insert a new item or
row into the collection or table
view.
So let's look at an example in
code to see how we can implement
this.
Inside of performDropWith
coordinator, we need to start by
gathering up a few local
variables that we need.
In particular, we'll need our
destinationIndexPath, and we'll
also use the local object
property on the drag item to
actually get at the underlying
data synchronously.
This is something that you can
do when the drag session starts
from your own app.
Next, because we have that data
right away we can perform an
immediate insertion into our
collection view, so we'll call
performBatchUpdates.
And inside, of course, we'll do
the usual update of our data
source incorporating that new
image.
And we'll insert an item in the
collection view.
And now, because we've actually
already inserted that cell, we
can tell the drop coordinator to
drop the item to that specific
cell at that index path.
This is going to set up that
nice animation you saw where the
drag preview morphs right to the
final cell that we inserted.
All right.
The next drop animation is
dropping into an item or row.
As you might imagine, this is
generally used with the intent
insertIntoDestinationIndexPath.
In this case, we're inserting
the item inside of a container
cell.
And so the item is going -- we
want the animation to kind of
scale down with this nice
animation right to some region
inside of that cell to show the
user where this drop was
accepted.
So let's look at an example of
how we could set this one up in
some code.
Once again, we're back in our
performDropWith coordinator and
we have basically the same code
here that you saw from the
previous example.
So just gathering up our
destinationIndexPath and the
image that actually was in the
drag session.
Now there's one additional check
that we do want to do, in this
case, which is to make sure that
that destinationIndexPath
corresponds to an actual row in
our table view.
Otherwise, we can't drop into
anything in that album.
If it does, we can actually
incorporate the data inside the
underlying data structure for
that photo album.
And then we're ready to set up
our animation.
So, in this case, we'll get the
cell for that particular row and
we're going to animate to the
image view in that cell.
So we'll get the image view.
Then we call coordinator drop
item IntoRowAt index path rect.
We're passing a rect that it's
in the coordinate space of the
cell, so we translate that image
view's bounds up to the cell's
coordinate space.
So to recap.
Now, we've talked about two drop
animations.
We have dropping to a newly
inserted cell.
We have dropping to a rect
inside a container cell.
There's a third drop animation
method available which is
dropping to a target.
This is how you can perform
completely custom animations.
For example, animating to a
location anywhere in your app
with an optional transform.
So maybe you want to animate to
a tab bar or bar button item or
something like that.
Those are the three basic drop
animations.
But I'd like to step back to the
first one that we talked about
and revisit that just for a
second.
Dropping to a newly inserted
item or row.
As we've discussed, you actually
need to already have performed
the update on the collection or
table view to insert that new
cell before you can use this
method.
But what if the data hasn't
loaded yet?
If you're dragging between
applications on iOS, we always
have an asynchronous data load.
But these animations need to be
specified immediately before you
return from performDropWith
coordinator.
So to solve for that, that would
mean you'd have to do some
pretty difficult bookkeeping,
because you're going to have to
do temporary insertions in the
collection or table view, just
to set up these animations.
Which, of course, would mean
updating your data source with
temporary model objects, and
that's not going to be easy.
Remember these asynchronous
loads can even come in out of
order.
We know this is a big challenge,
and we are extremely excited to
tell you about brand new
technology in iOS 11 for
collection and table view,
specifically designed for this
problem.
I'd like to introduce
placeholders.
[ Applause ]
Okay. What are placeholders?
So, as the name suggests, these
are temporary items or rows that
you insert into your collection
or table view.
And the key thing about them is
that you can defer updating your
data source until the data
finishes loading when you insert
placeholders.
So for placeholders, you can use
any cell that you want.
You can provide it to us.
We'll do all the difficult
bookkeeping on the back end
until your data finishes
loading.
The way this works is that
collection and table view will
never actually call out to ask
your delegate or data source
about placeholders, so your
delegates can remain blissfully
unaware that they even exist.
You can insert as many
placeholders as you want,
anywhere you want in the
collection or table view, and
you can even perform incremental
updates while placeholders exist
to put new rows or items in the
collection view, table view, or
move some existing one.
Anything like that.
So this provides users with a
great experience while their
data is loading across
applications, because you can
keep your user interface live
and responsive during these data
transfers.
So how do you create
placeholders?
Well, you're creating them using
the drop coordinator, of course.
To insert and animate it to a
placeholder.
Let's dive into some code and
see how it's done.
So we're back in our
performDropWith coordinator
method.
Again, we'll need a
destinationIndexPath that we're
actually going to drop to.
And now here we do things a
little bit differently.
Now we're going to iterate over
each of the items in the drop
session independently and for
each item, we're going to insert
a placeholder.
So we call coordinator drop item
toPlaceholderInsertedAt index
path withReuseIdentifier.
This will actually insert a
placeholder at the
destinationIndexPath for this
particular item.
You'll notice we pass over use
identifier.
This is something that you've
already registered with the
collection or table view and
we're going to use that to
dequeue a cell for this
placeholder.
You'll also notice, of course,
that there's a closure at the
end and that there's a comment
configure your placeholder cell
here.
Well, as you recall, collection
and table view, they're not
going to call out to your
delegate or data source about
placeholders.
That mean you're not going to
get a call for cell for row or
cell for item in index path for
placeholders, so this closure
serves as your replacement for
the configuration that you might
normally do in cell for row at
index path or cell for item in
index path.
Okay. There's one more thing.
Once you've inserted the
placeholder, it actually returns
something back to you, and
that's this placeholderContext.
What is this?
The placeholderContext is
returned for each placeholder
independently.
And this is how you're actually
going to commit the insertion of
the placeholder to exchange it
for the final cell once your
data finishes loading.
Or, if the data transfer failed.
Or maybe it was cancelled by the
user.
You can use the
placeholderContex to actually
delete the placeholder because
it'll no longer be needed.
So let's jump back to our code
and see how we use this
placeholderContext.
We're picking up right where we
left off.
We inserted our placeholder.
We got the placeholderContext
back.
Now we can actually go and load
the data.
Again, this is asynchronous, so
we use this load object of class
method to do that.
When the closure completion
handler runs, that'll be called
on a background queue.
So we make sure to transition
back onto the main queue before
we update our UI.
And now we can check did we
actually get any data loaded?
If we did, we'll have an image
in this local variable here.
And now we call commitInsertion
on the placeholder context,
which again, we'll swap out that
placeholder cell and insert the
real cell at this particular
location.
Now you pass a closure to
commitInsertion.
Inside of that closure, it's
your responsibility to update
your data source to incorporate
the new data for the actual row
here.
Or item in the collection view.
Now one other important thing.
Take a look at that index path
that's being passed inside that
closure, the insertionIndexPath.
You need to be very careful to
actually use that one and not
the original
destinationIndexPath that you
originally inserted the
placeholder at.
This is because between the time
when you inserted the
placeholder and when the data
finished loading, if other
updates happen in the collection
or table view, the placeholder
might have moved.
And so you use this index path,
which we provide to you, to tell
you the final location when the
placeholder insertion is
committed.
Okay, one other thing.
Of course, if our data transfer
didn't work for some reason, or
maybe it was cancelled, we can
just delete the placeholder
because we no longer need it.
Now one quick note about working
with placeholders.
It's very important that you
avoid calling reload data on the
collection or table view when
you have placeholders.
Instead, you should use
performBatchUpdates.
This is so you can perform
incremental updates on the
collection or table view.
And the reason why you want to
avoid reload data is because
that just resets everything in
the collection or table view.
And, as a result, it's going to
strip out any existing
placeholders that you still
have.
If you use performBatchUpdates,
you'll be performing incremental
insertions and deletions, and we
can keep those placeholders
alive while you update your
collection view in real time.
You can use the new property
hasUncommittedUpdates on
collection and table view, if
you would like to check if it
has placeholders still around.
With that, I'd like to hand it
back to Mohammed to show you an
amazing demo of all this
placeholder goodness in action.
Mohammed.
[ Applause ]
Yeah. I can hold it.
>> Thanks Tyler.
Wow. Placeholders sound amazing,
don't they?
I can't wait to actually build
them in our app and build a
really awesome drop animation.
Let's get started.
So the first thing we'll want to
do is implement another optional
method on the drop delegate to
indicate to the collection view
that we'd like to animate our
dropped items to the drop point.
So let's add an implementation
for dropSessionDidUpdate
withDestinationIndexPath.
And we'll start returning a
collection view drop proposal
with a drop operation of copy
and an intent of
insertAtDestinationIndexPath.
Next, we'll head back to our
performDropWith coordinator
method and we're going to remove
our existing coordinator code
because we're going to do
something a little different.
The first thing we'll want to do
is iterate the coordinator's
drop items or self, and we're
going to check if each drop
item, drag item item provider
can be used to load an object of
class image.
If it can, then we'll do a
couple of things.
First, we'll ask the coordinator
to drop a placeholder at the
destinationIndexPath.
And we'll pass it a reuse
identifier for a placeholder
cell that we've registered with
the collection view previously.
We're going to hang onto the
placeholder context, because
we're going to need it to update
the collection view later on.
Next, we'll actually trigger the
data load by calling load object
on the item provider and
loadObjects is completion
handler.
We'll go ahead and handle the
drop.
Note that I'm dispatching back
to the main queue here, since
loadObjects is completion
handler is called on a
background queue, and we're
going to be updating our UI.
If the data transfer succeeded,
we'll finalize the drop by
calling commitInsertion on the
placeholderContext.
And we'll pass it a set of data
source updates that use the
insertionIndexPath to insert our
new image in our [inaudible].
Then if the data transfer failed
for some reason, we'll make sure
to clean things up by calling
deletePlaceholder on a context.
There's one more thing we should
do here and that's to disable
the default system progress UI
for long-running data transfers,
because we have our own inline
one now.
So we'll go ahead and do that by
setting the drop session's
progress indicator style to
none.
That's it.
Let's see what it looks like on
our device.
So to get the full effect of
what we've just done, I'm going
to -- instead of dragging out a
photo, I'm going to switch over
to a different app, which I've
called Slow Photos, which
intentionally slows down data
transfers.
I'm going to pick up a few of
these images here and I'm going
to head back to my collection
view.
Notice the first thing you'll
notice here is that when I hover
over the collection view, the
cells start moving apart and we
get this cool kind of
springboard-like reordering
look.
This is because we started
returning the
insertAtDestinationIndexPath
intent.
If I let go of the dropped items
-- oh, I love that animation
[laughter].
So note that the first thing we
get are the placeholder cells
with this inline progress UI.
And a few seconds later, when
the data transfer completes, we
actually have the cells, the
real cells with the real images
loaded.
[ Applause ]
So now I'm going to hand it back
to Tyler who's going to talk
about some final touches that
can really help you make the
drag and drop experience in our
app uniquely awesome.
>> Okay. Thanks Mohammed.
So let's wrap up by quickly
jumping through some of the
final touches that you can adopt
using our wonderful APIs here.
The first one that might be very
interesting to you is supporting
reordering.
So now that we have Drag and
Drop, it might be obvious that
you might want to use that for
reordering.
Well you can of course do that
and it's really easy.
You start by implementing the
dropDelegate method,
dropSessionDidUpdate
withDestinationIndexPath.
The reason why, of course,
that's how you provide your drop
proposal to the system.
And, in particular, you need a
very specific drop proposal if
you like reorder, which is the
drop proposal with an operation
of move and an intent of
insertAtDestinationIndexPath.
That serves as a signal to the
collection or table view that
you're interested in supporting
reordering, when the item that's
being dragged comes from the
same source view.
Now here's where things get a
little bit different for table
view and collection view.
We'll start with table view.
With table view, table view has
already supported reordering for
a very long time and you're
familiar with this very
longstanding data source method.
Table view moveRowAt IndexPath
to IndexPath.
You can continue to implement
this, if you like, to support
reordering, using Drag and Drop.
Because table view is actually
going to call this instead of
calling through perform drop
with coordinator, if you've
returned that magic drop
proposal and a single row is
actually being reordered.
This makes it really easy,
because you can use the same
code to ship on an iPhone.
For example, maybe where you
don't have Drag and Drop using
the existing style of
reordering.
And, if you are on an iPad, you
can use the new style of
reordering here.
So that's table view.
Pretty straightforward.
Collection view is also equally
easy, though.
Collection view.
You just need to make sure
you're implementing both a drag
and drop delegate.
So both sides.
And inside of perform drop with
coordinator, you'll actually
look at the drop items that we
passed to you in that
coordinator.
We actually are providing a
sourceIndexPath for each of
those items, which will be
non-nil, if the item represents
a particular item in the
collection view that it came
from.
So you can delete the item at
that sourceIndexPath and insert
a new item at the
destinationIndexPath.
And that's going to affect the
actual reorder.
So you'll have that cell move
seamlessly from the start to the
destination.
Now collection view has some
extra functionality for
reordering, though, and that's
this new concept of reordering
cadence.
As you know, collection views
often have these two-dimensional
grid-like layouts.
For example, the flow layout you
see behind me.
And when you're reordering,
sometimes you can get the kind
of behavior where the items are
reflowing out as you're moving
your finger.
And that's great when you're
actually trying to reorder.
But you don't always want that
behavior.
Maybe if you're trying to just
drag to a different location
outside of the collection view
and it happens to support
reordering.
So the reordering cadence has
three different values to help
you tune how responsive the
collection view is going to be
when it shuffles and reflows its
layout.
So the default, of course, is
immediate, which you've seen in
the demos so far.
Where once you start moving
around, it immediately is going
to reflow the collection view
layout as you're -- you know,
almost in real time reordering.
If you'd like to add a little
bit more deliberance here, you
can switch to the fast mode.
Where, if you move very quickly,
it won't actually immediately
reshuffle all the layout around.
And so you can see you have to
pause just a little bit longer.
And then for slow, it
exaggerates that even further.
So the user really has to be
deliberate about wanting to stop
and reorder to a specific
location.
This is how you're going to get
behavior very consistent with
the behavior you see on the iOS
Home screen when reordering app
icons, for example.
So that's reordering.
Let's talk about spring loading.
Spring loading, as you probably
know by now, is a way to
actually navigate and activate
controls throughout the system
while you're in the middle of a
drag session.
You just hover over the
controls.
It starts highlighting and then
it's going to activate.
So, in collection and table
view, it's dropped at easy to
adopt spring loading and that's
because both of them conform to
the protocol UISpringLoaded
InteractionSupporting.
Basically that just means they
each have a property
isSpringLoaded, which you set to
true, and spring loading works.
When users spring load on a
particular cell, we're going to
call -- we're going to select
that item or row in the
collection or table view.
And you can customize spring
loading using a new delegate
method shouldSpringLoadItemAt
IndexPath with context.
This is how you can opt out
specific rows of spring loading.
Or you can even customize spring
loading effects a bit using that
context.
For example, you can change the
view that actually flashes
during the spring load
interaction.
So next, let's talk a little bit
about customizing appearance.
We have some new ways that you
can customize the appearance of
cells that are participating in
drag sessions.
So cells start -- they have a
new notion of a drag state, and
they all start in the none
state, and that's before
anything's happened.
But when the user actually puts
their finger on the glass and
starts lifting one of those
cells, we're going to transition
the cell to the lifting state.
And, as you can see here, in
this example, we have a little
banner showing on each of the
cells.
And we'd rather not actually
show that banner when the user's
dragging the cell all around
across the system.
So we can use this transition of
the state to the lifting state
to actually hide that banner.
When the user actually moves
their finger to actually begin
the drag session, once the cell
finishes the lift, we'll
transition the cell that remains
behind in the collection or
table view to the dragging
state.
And by default, this is going to
result in a faded appearance,
where we'll reduce the alpha of
that cell.
Now you can respond to these
state transitions by overriding
the method on your cell class,
subclass, dragStateDidChange.
Just use the new state that's
being passed in.
You can optionally call super to
get the default appearance and
behavior.
Or you can just choose to
override it and provide your own
behavior and implementation.
We will even make sure to call
these state transitions inside
and alongside animation closure
with the actual lift animations,
for example.
So if you make changes here,
they're going to be animated
running forwards and backwards
with those lifts.
So that's how we customize the
actual cell that is in the
collection or table view, but
what about the actual drag
preview that you drag around?
As you see here, in this
example, we have a cell that is
basically a square shape.
But, if the content that's
visible is a wider aspect ratio,
it's showing a nice image and so
we result in, you know, these
not very pretty white bars at
the top and bottom.
Probably don't really want this
appearance while the user's
dragging this, you know, photo
around the system.
So how can we fix that?
Well, by default, it's happening
because we use the entire cell
as the drag preview.
But, of course, you can change
that.
And so you can provide drag
preview parameters by
implementing an optional method
on the dragDelegate,
dragPreviewParametersForItemAt
IndexPath.
And, if you provide these,
there's, for example, an
opportunity for you to provide a
bezier path, which will clip to
a specific region within the
cell.
So if we look back at our
example, if we return a bezier
path that clips to the actual
rect where there's a visible
photo, we can just lift just
that photo off the screen even
though the cell is still a
square in this example.
[ Applause ]
Okay. Wow.
We've covered a lot today.
So what are the next steps for
you?
Well, first of all, we hope when
you leave here you go and add
Drag and Drop to all the
collection and table views in
your applications.
You saw how easy it is just to
get the basics up and running
very quickly.
And users are going to expect
that your apps support Drag and
Drop in iOS 11.
Now you should definitely
consider providing a drop
proposal and setting up those
great animations, because we
made it really easy to do so.
And it will provide the best
look and feel for your users.
And, of course, don't forget
about placeholders.
When your data is loading
asynchronously, use placeholders
to very easily manage that
asynchronous data load and keep
the UI completely responsive and
interactive during any
long-running transfers.
And finally, don't forget to
polish the details.
We can't wait to see what you
will do with all of these really
awesome APIs, and we're going to
be super excited to see the
results.
For more information and to
download the sample app that
we've been showing you today,
which has fleshed-out examples
of all sorts of great Drag and
Drop with collection view and
table view code.
Go to this URL, it's really
great.
There's a session immediately
following this one, in this room
right here, so you can just stay
seated.
Data delivery with Drag and
Drop, which is going to go into
some more details about loading
and transferring the actual data
and item providers.
Really cool.
Stick around for that.
And, of course, we had some
other sessions earlier this week
on Drag and Drop, introducing
Drag and Drop, and mastering
Drag and Drop.
Check those out on video to
learn more.
Finally, we hope you've enjoyed
the presentation and have a
great WWDC.
[ Applause ]