NSCollectionView has been displaying grids of views for many years. OS X 10.11 brings a major update to the capabilities of NSCollectionView. Much of the design of UICollectionView in iOS is now available for OS X apps. Understand the details of the enhancements and how you can harness them in your apps.
TROY STEPHENS: Thank you for coming.
Welcome to Session 225, What's New in NSCollectionView.
My name's Troy Stephens, I'm a Software Engineer
on the AppKit team and I'm delighted to get
to answer this question for you today, what's new indeed.
Let's find out.
NSCollectionView has been around for a while on OS X since 10.5,
and it provides a handy way to display a grid of items
in the user interface, for example here,
in the Screen Saver Pref panel, we have a grid
of items representing the different screen
CollectionView is good at this, displaying a grid
of identically sized items,
you give CollectionView an item prototype, which consists
of basically, a view subtree of your own design,
and an associated ViewController that manages it.
CollectionViewclones that item prototype, to populate itself
with items that represent your model objects.
CollectionView supports selection, drag and drop,
animated re-layout, all around,
it is a very handy class to have around.
Now enter UICollectionView on iOS,
a cousin to NSCollectionView that is also very versatile.
We see it here in the World Clocks part
of the iPad Clocks app.
UI CollectionView, like its name implies,
is useful for displaying collections of items,
where each item, once again, is represented
by a view subtree that's completely of your own design,
usually, these are loaded from a nib, withUICollectionView.
And UICollectionView supports mixing item types,
you can have different nibs, prototypes for different items,
you're not stuck with just one.
UICollectionView supports optional header views,
and footer views surrounding your items, and bracketing them.
This is especially useful when used in conjunction
with the ability to group your items into sections if you wish.
Each section can have a header, and a footer view.
Layout is very flexible and customizable,
there is a default flow layout that handles about 90%
of your needs and is very customizable,
you can tune its parameters to usually get something close,
at least, to what you want.
UICollectionView is also open to arbitrary,
Any kind of layout algorithm that you can implement,
UICollectionView can use,
to apply to the items that it shows.
Importantly, UICollectionView is scalable.
It has smart behaviors in it, so that it can scale
to potentially large numbers of items, and it is smart
about just instantiating the items for model objects in view,
and recycling or reusing items that have scrolled out of view
to represent other model objects
that have been scrolled into view.
That's very handy.
Like everything else on iOS, UICollectionView was designed
from the beginning to always operate layer-backed,
which gives it the ability to present very fluid,
high-frame-rate animation, as the items
in the Collection View move around, and come and go,
it can give you have a very nice, animated effect.
With all of these great features,
and an API that developers have already become familiar with,
and wide adoption and use through iOS apps,
we thought that UICollectionView would therefore form an
excellent basis for the new
and greatly-improved NSCollectionView
that we're introducing in El Capitan.
This new NSCollectionView inherits all
of the scalability behaviors of UICollectionView.
It knows how to instantiate items only as needed,
just keeps a few of them around, a little more
than what's visible and is able to reuse or recycle items rather
than reinstantiate them.
That saves some overhead, you can group items in sections,
you can give those sections header, and footer views,
if you want, put anything you want in them.
Layout is completely customizable,
so NSCollectionView is no longer hard-wired to think I'm a grid.
You can plug any kind of layout you want into it.
We can handle items that are of variable sizes, the items can be
of all different types, you can mix and match,
and Flow Layout especially is capable
of handling that very gracefully.
Appearance, as before, it is completely customizable,
you can define your Item View subtrees
to look however you want.
The CollectionView is your blank canvas, to use however you want.
You have control over animations.
When animations are performed, and with what durations.
We'll see how to do that.
Of course, not content to just port UICollectionViews
capabilities to OS X, we wanted
to make the new NSCollectionView feel really right
at home on the desktop.
One of the important technologies we deal
with on the desktop is drag and drop
which NSCollectionView has supported all along,
but as part of bringing UICollectionView Layout API
to the desktop, we have augmented the API with a couple
of new methods we'll talk about at the end,
that basically enable any layout,
including your own custom layouts, to perform hit-testing
to identify drop target candidates.
We'll look at how to do that later.
Any custom layout of your own design can support Drag-and-Drop
like a first class layout.
Rubber Band Drag Select, you use that to drag across items,
and select groups of items in bulk,
that's fully supported as before.
We have modified the selection
and highlight notification methods, the delegate methods,
that handle those occurrences, to be able to handle selection
and highlighting of items in bulk because that's something
that tends to happen a lot more on the desktop, you know,
you tend to do a Select All operation or a Deselect All,
or a big Range Select.
We wanted to be able to handle those very efficiently.
There is slight adjustments to the APIs.
As before, however, items are still represented using
ViewControllers on the desktop.
We think this provides a great opportunity for you
to compartmentalize your code neatly,
and have your controller code separate from your view code.
Better code organization.
One last nice little touch we added, is the ability
to automatically find the appropriate nibs to use
for items, if you just follow naming conventions,
if you name the nib identical to the identifier
of the item type you're requesting
from the CollectionView, you don't even have
to register your item nibs with the CollectionView anymore.
It saves you a little work, a little bit of code.
So our goals for today, I wanted this
to be a really hands-on session, I want you to walk away ready
to use the new CollectionView.
We'll look at how to wire up one of these new NSCollectionViews,
where we're using the new API on 10.11, along the way,
we'll learn what's different on OS X versus on iOS,
and we'll have a little something
for everybody here I think.
Whether you're a veteran iOS developer,
maybe bringing companion apps over to OS X,
you have used UI CollectionView, you're familiar with the API,
you want to port that knowledge over, maybe some code over,
we'll have information useful to you.
Maybe you're an OS X developer working
with NSCollectionView already, and you want
to learn the new API paradigms and how to do that.
We'll have enough of an introduction
into the UICollectionView APIs to get you started.
But even if you've not worked with either before we're going
to have enough introduction, and enough nuts and bolts
and a code sample, to go along with today's session
that you can study, to learn how to get up and running quickly
with the new CollectionView.
We'll start with a quick overview of the concepts,
the basic concepts that we need to understand about the new API
and then we'll dive into nuts and bolts of how the API works,
which methods are important, what to use, and we'll wrap
up with a quick conclusion.
First with that overview.
In the old model of using CollectionView, NSCollectionView
on OS X 10.10 and earlier,
you would wire your CollectionView's content
property to an array or array controller
that references your model objects, that's how you wire
up your model, and you provide an item prototype,
that's an NSCollectionView item, a subclass of ViewControllers,
you have basically got a ViewController,
and an associated view subtree, and this is what's going
to get cloned to populate your CollectionView.
Lastly, yourCollectionView might have a delegate,
if you want to support Drag-and-Drop,
that's your delegate's responsibility,
so you would wire up a delegate for that.
With the new API, a few of these things have changed.
Instead of providing a content array, what you are going
to do now on El Capitan, is wire up a data source.
The data source protocol for CollectionView is very simple,
as in iOS there are only two required methods,
it's very quick and easy as you will see to wire
up your model to CollectionView.
You still have a delegate, as before,
but now the delegate has the opportunity to participate
in selection and highlighting of items.
We'll talk about that in detail later.
Instead of an item prototype you now typically provide a nib
file, that hasyour example item in it,
and it's associatedview subtree.
You're not limited to just one of these, as I said, you can mix
and match different types of items,
you can have multiple nib files, one for for each type of item
and the view trees can be completely different for these.
Last but not least, very importantly,
we have taken the layout functionality and factored it
out of CollectionView so it is no longer hard wired.
We now have a very modular model, like on iOS,
so you can take your existing CollectionView,
unplug the layout that's currently connected to it,
plug in a different layout, and suddenly your items are laid
out with different sizes using a completely different layout
algorithm, and these layouts are completely interchangeable,
nd you can even make the change to a different layout
in an animated-transition kind of way.
very easily, as ourcode sample does.
For customizing layout, your layout can also delegate
to your CollectionViews delegate.
You can implement certain optional methods to allow you
to make per-item layout adjustments.
You don't even necessarily have to subclass,
if you want a custom layout.
We'll take a quick look at the layout classes.
These arevery similar but not identical, on iOS.
As on iOS, NSCollectionViewLayout,
his is the base class that defines the common behavior,
the API interface for all layouts.
NSCollectionViewGridLayout is new and unique to OS X
and its job is basically encapsulate NSCollectionView's
existing, sort of stretchy grid layout algoritm,
in case you want to use it with the new APIs.
It gives you a stretchy grid, where it tries
to make all items the same size, but they're limited to min
and max sizes, and it tries
to basically fill its visible area as much as possible.
Sometimes useful, but this is sort
of a legacy layout right now.
It doesn't yet support sections or header or footer views
and FlowLayout is generally much more flexible.
We usually recommend that you you start with that.
NSCollectionViewFlowLayout is basically identical
to UICollectionViewFlowLayout on iOS, it's a very powerful,
general layout algorithm, and the algorithm is much
like flowing text fragments or CSS boxes in flow,
if you've dealt with either of those problems.
Basically, you can have variable item sizes,
and the layout algorithm canhandle that nicely.
It willrack up items into rows or columns,
depending which orientation you give it.
When it fills up a row or column,
it'll wrap to a new one and continue on.
FlowLayout supports sections with optional header
and footer views, and it is generally very powerful
and customizable, so even if you do need to subclass,
usually you'll want to start with FlowLayout, and subclass
from there to get what you want.
You're always free to subclass NSCollectionViewLayout,
to get a completely custom layout.
Our sample code todaydoes just that.
Layout attributes objects aren't always immediately intuitive
to those who look at them for the first time.
This is a concept -- the same concept as on iOS,
and once you understand what it is, it really is very simple.
Imagine that you can take a view's frame
and other assorted properties
and encapsulate them separately from the view.
That's what LayoutAttributes' job is.
You have an instance of these frames,
the most obvious one , right?
you need to know the position and size of an item,
but there are other ancillary attributes, such as Alpha value,
Opacity in other words, Zindex for back-to-front sort order,
and whether the view is hidden or not,
which can also be considered layout state.
You're taking state, and snapshotting it,
what this enables the new APIs to do, is to reason about items
that are not currently instantiated.
Remember, we're just lazily instantiating items on demand.
We end up creating these, and passing them around,
these layout attributes instances,
that's what the Layout APIs deal with, they pass them around
and then eventually they end up getting applied to items
or views at layout time.
Applied in the sense of setting an animation target
for a transition to a new state.
That's all they are really.
You can group items, that's pretty straightforward,
you can break your items up into groups now,
each group can potentially have a header above it,
and a footer below it, or this also works sideways,
ifyou're doing that orientation.
The header, and footer, together with the items
that they bracket, constitute a section,
our first section here is section 0.
The items within that section are numbered 0, 1, 2, so on.
This is exactly the same as on iOS.
The next section can have a header, a footer view,
together with the items, now we've got section 1
and the items in that section are again numbered 0,
1, 2, and so forth.
So clearly we have a need for a different way to address items.
We're going to start addressing items like we do on iOS.
We need to know not only the item index,
but the section index.
This has consequences for existing APIs, and accounts
for a lot of the API changes that you will see.
For example, the ItemAtIndex method,
which takes a single-integer index,
is no longer sufficient.Now that we have sections,
we need to know the section number too.
So APIs like this are now soft-deprecated in favor of ones
that take index paths.
And NSIndexPath is just an existing value type
that lets us very conveniently encapsulate a section index,
and an item index, together in an object, a value object
that we can pass around, throw into collections, so on.
A lot of the API changes you will see actually are just
accountfor this simple transition away
from single item indices.
You can still use the old APIs
if you have a single-section CollectionView,
which is the default, but if you might be using sections,
we encourage you to use the new APIs.
We're obviously starting to get into nuts and bolts,
we'll embrace that, and get down to looking at our example code.
The example today is CocoaSlideCollection,
and it's basically an image browser
that uses a CollectionView to present a folder
of ImageFiles for you to look at.
For each ImageFile in the folder,
we show a thumbnail image and assorted image info.
We position these using the Flow layout that comes stock
on the system, and our own custom layouts,
that you will see how to implement.
We're going to suppose that each
of our Image Files can have tags associated with it
and we're going to use that as an excuse
to show off the ability to have sections with headers
and footers, we're going to be able togroup our items by tag.
We'll also support selection and Drag-and-Drop
of our items using the APIs that we'll discuss today.
To break this down into parts, we'll break it
down into six parts, so we can do this step by step.
First, of course, we want to make items appear.
That's always nice.
That's the big hurdle.
When you get stuff to show up, you can get more advanced.
We'll do that quickly.
Thenwe'll look at grouping those items into sections.
Next we want to look at how do we handle it
when the model changes, the ImageFiles come and go,
how do we update our CollectionView.
We'll see how to do that properly.
We'll look at handling selection, and highlighting,
of the semantics of that, handling Drag-and-Drop and last
but not least, the really fun part, we'll look at how
to make custom layouts of ourown.
First, making items appear, back to our model here.
This is the new API again.
We need to provide a data source
that implements those two required methods.
We'll need to provide an item nib, simple enough.
Then a CollectionView layout.
The two required methods are simply give the CollectionView
the ability to ask how many items are in this section,
by default we have one section, it is going to pass 0
for the section index, it'll just return the number of items.
The second method's responsibility,
is to actually instantiate items,
or it could be actually reusing the recycled items
under the hood, with help from the CollectionView
and return them back to the CollectionView.
In CocoaSlideCollection our basic model object
to understand is the ImageFile.
An ImageFile basically references a URL of an ImageFile
that we found on disk in the folder we're scanning,
that includes the file name that we're displaying at the top
of the slide, the file's type
which we displayed a user-readable description of,
the pixel dimensions of the original image,
then a thumbnail, of course.
You will see those around in the source code.
An image collection owns an array of ImageFiles,
An image collection also owns an array of tags,
each of which owns an array of ImageFiles that have
that tag applied, and there is an untagged ImageFiles array
First we'll look at making items appear.
We'll go over to the demo machine here.
Let's get this up and running.
We'll open our Xcode project.
Things are, you know, mostly ready to go,
but we have no CollectionView in our window.
That's going to be a problem.
We'll look at how to actually get a CollectionView.
So going into our Resources group here.
We have a BrowserWindow nib that holds our main window.
Let's look at that.
We'll go in the library here, and search for a CollectionView.
We'll drag it out.
We'll size it to fill our window.
Apply constraints to it.
That's going to be our main document view for this window.
Okay. Now what we did, what we actually got when we dragged
out a CollectionView, this is a lot like working
with a Table View or Outline View,
you're actually getting a CollectionView embedded
in a scroll view unlike on iOS.
Scroll view is a separate thing that's composed
with a document view that you want to scroll,
you don't inherit the scrolling behavior,
it is done through composition.
We have a scroll view.
I said that the new CollectionView is designed
to run layer-backs, we're going to go over here
to our inspector, and we'll set the [indecipherable] property
on the scroll view to ensure layer-backing.
Now, we'll drill down to the CollectionView itself,
which is the scroll view's document view.
We have a new properties inspector here in Xcode 7
that supports some of the new capabilities.
We choose a layout we want to use, such as Flow,
and even set its properties.
In this sample app we're actually going
to switch programmatically between the layouts,
so we don't really need the one with we unarchived from the nib,
but we can set that there.
You can do fun, simple things, like set the background color,
as soon as I find the color panel.
The more interesting thing, is that you have to hook
up that data source, right?
So, our file's owner in this project is an instance
of APL Browser Window Controller.
We have a window controller that manages the window,
that's also going tp be our data source, and our delegate
for our CollectionView.
We'll wire that up here from the CollectionView
to the file's owner, it's going
to be the CollectionView's data source.
It will be its delegate too, to so we can handle Drag-and-Drop,
we'll wire from the file's owner back to the CollectionView,
we have an ImageCollectionView outlet that we've defined
to make it easier to find our CollectionView.
That's basically what we need to do in our nibs.
We have also got a slide nib that we created.
It holds basically a container view that's referenced
by a slide, our slide class is a subclass of collection,
We've subclassed so we can add some functionality there,
our own custom controller functionality,
the root view is just a container that will be sized
by the CollectionView's layout algorithm.
Then, we have controls,
text fields that have autolayout constraints set on them,
relative to their containers.
The layout will set the frame of the item, the item's root view,
and the rest will all be done by autolayout internally.
I have also used bindings
to basically connect the value displayed by each
of these text fields, through the slides-represented object.
Remember a slide is a CollectionView item,
therefore it is a ViewController,
ViewController has a represented object.
That's where we're going to connect our item
to the model object it represents,
to the ImageFile instance.
We can access properties of that instance.
All we really are going to need to do is wire our item
up to the represented object, i.e. its ImageFile, and then all
of these controls will just populate,
including the image view over here.
Because we're using a separate nib file for the modern API
for CollectionView, we're going to prune some stuff
that Xcode put in here by default still.
Our image CollectionView still has an item prototype.
We want to get rid of that.
Let's unwire it.
That will interfere with what we're doing.
We'll delete the item prototype,
we'll delete the views associated with it.
We don't need those.
We can build and we're almost okay.
If we look in the warnings here, we see a reminder,
we didn't implement the required data source methods,
there are just two of them, so let's go do that real quick.
We'll go to Browser Window Controller.
Look here, where the data source methods should be.
Fortunately I typed some in advance.
We'll just drag in.
For a non-section CollectionView this is very simple,
we implement CollectionView, number of items in section
by default, the CollectionView assumes one section,
we return the count of ImageFiles
in our image collection, and the other thing we need
to do is make items on demand.
The CollectionView will send us CollectionView Item
For Represented Object At Index Path.
The important thing here is that we're calling back
to the CollectionView, and saying make item
with identifier, which is a little misleading,
make really means make or give me
than existing one you can recycle for index path.
We just pass in the index path that we were given,
that identifies the item.
As I said, we want to wire up the items represented objects,
so we can find the corresponding properties to display just
for that item instance.
We've got a little method here that we factored out,
ImageFile At Index Path, that lets us dig
into our data model real simply,
and find the corresponding ImageFile instance.
That's all we need to do, return the item back
to the CollectionView.
If we're feeling brave maybe we can build and run this,
and see if it actually works.
So, we're going to come up with -- there you go.
By default we have a window pointing
at library desktop pictures, and it scans that folder.
It looks for the ImageFiles
and presents them using CollectionView items
and we scroll through here, the items that were out of view,
are actually being instantiated on demand, or even recycled
from items that just scrolled off the top.
You can resize, the layout reflows
and we get a lot of stuff for free.
This is, I call it the wrapped layout here,
it is a simple subclass of the Flow layout,
then we have some custom layouts that we have implemented, -
we can layout - these are implemented
by plugging a different layout object into the CollectionView.
We have got group by tag here,
but that doesn't really do anything yet.
Let's go back to the slides
and see what it takes to get that working.
That's always a good start, to get the first demo running.
Now we want to group the ImageFiles by tag, not satisfied
to display them in one section, we want to see
which ones correspond to which tags.
What we're going to do, for each tag we have an array
That are implicitly ordered in some way,
and we want to show them in that order.
An ImageFile may have many tags, meaning we'll show it
in more than one section.
We also may have ImageFiles that don't appear in any section.
We are going to have an untagged ImageFile section,
one additional section at the end.
Where we show all of the ImageFiles
with that have no tag.
We'll give each of our sections a header and footer view,
because we want to show off that we can do that.
Show you how to do it.
As with item types, the instantiation process for header
and footer views, you'll see, is very similar.
A header or footer is in general considered what CollectionView
calls a supplementary view, it doesn't represent an item,
but [is] something that sort of augments,
or brackets the display of items such as a header or footer.
We're going to implement the data source as optional,
CollectionView, View For Supplementary Element OF Kind,
that is a fancy way of asking, in our case, for a header
or footer, with a given index path.
We'll go back to the demo machine for that.
We'll hook this up real quick.
We'll need to take our existing data source methods
and replace them with slightly more sophisticated ones,
that understand how to deal with sectioning.
Here they are.
We'll go through them briefly.
Now we need to be able to tell the CollectionView how many
sections there are in it.
If our GroupByTag check box is checked,
which with will set the property,
we just return the count of the number of tags
in the image collection.
Plus one because we want
that extra untagged ImageFiles collection section,
sorry, at the end.
Reporting the number much items in a section,
again it is a little different if we're grouping by tag,
basically we want to say if the section corresponds to one
of our tags in our collection, we'll return the count
of the number of ImageFiles in the tag
that corresponds to that section.
If we're in the special untagged ImageFile section
at the end we'll return the count
of untagged ImageFiles, and so on.
It's pretty straightforward.
We'll look at Item
For Represented Object At Index Path.
This is the same implementation as before, it is able to be
because I factored out this ImageFile At Index Path method
for my own use which, actually, this one is not suitable
because it looks at the item index and not the section index.
We'll replace it with a smarter version
that knows we might want to group by tag.
If we're grouping by tag, again, if the section corresponds
to one of our tags, we'll find the ImageFile in the list
of ImageFiles for that tag, according to the item index,
and the section index that tells us which tag we're dealing with.
Let's try building and running now.
We can check group by tag here
and now we have our items grouped by tag
and we get the same flow and re-layout as before,
and if we zoom in we can see.
This is our header view which we defined in a nib,
it is basically a container, gives us a light gray background
and has a text field, you can put any kind
of controls you want in here in these,
this is our footer view here, this is sort of a darker gray,
telling us - we have put a text field
in telling us how many ImageFiles are in the group.
That's really all it takes to implement sections,
it is basically the same as on iOS.
the one thing that I elided here, oh, two things I elided,
are the creation of the supplementary views, the.
headers or footers, there is a bit of code here,
but basically it is really very parallel.
The main point of interest is where we call back
to the CollectionView and say, make Supplementary Element View,
sorry, Make Supplementary Element View of kind,
the Flow layout defines,
are section header, and section footer.
It will be one of those.
We know when it is section header we'll look
for header.nib, we're going to look
for footer.nib if it is the footer.
We pass that in as an identifier.
Once we've got our view, we're getting a view,
and not a ViewController this time.
We can find and set up the value for TextField,
do whatever we want, and return it back to the CollectionView.
Last thing we need to do is implement these delegate methods
the Layout uses to figure out what's the proper size,
basically the height to display the header at, and the height
to display the footer at.
We have NSIs in each case,
and since we have a vertically-scrolling flow
layout, only the height matters, the width will just get clipped
to the width of the scroll view.
We had to do that to make sure that ourheaders
and footers showed up.
Now we can move on to updating when our model changes.
So ImageFiles will come and go in the folder,
we need to tell the CollectionView
when our model changes so that we can update what it is showing
This is done very similarly to the way that this is handled
with Outline View on OS X.
Basically, these are the four operations:
Items can be inserted, deleted, an item can be moved
from one place to another, or an item can be reloaded
which basically means it is still there
but the properties have changed, you need to redisplay it,
you need to regather properties from it.
It turns out that these operations apply
to sections as well as items.
This is the same as on iOS for those who are familiar.
You can insert, delete, move, and reload sections as well.
Similar approach to view-based outline view as I mentioned,
which basically means any time the model changes,
it is the responsibility of your data source or some other part
of your code that deals with the model,
to notify the CollectionView,
describing exactly the changes that were made.
I inserted items at these index paths.
I removed items here, so that it can will keep up,
staying in sync with the model.
If you do it right, it is very simple.
By default, any changes you notify the CollectionView of,
will appear instantly but you can easily get an animated
change, by messaging through the CollectionView's animator,
this is a general proxy object that views have,
that you can message through, to request an animated change,
usually when setting a property in this case
when notifying the CollectionView
that items have been inserted.
That's what we do in our example and that's why we see items come
and go in animated way.
Items are inserted, other items move out of the way
in an animated fashion.
In our example, CocoaSlideCollection we're going
to watch our ImageFolder for changes,
when changes occur we're going to notify--
first we immediate to update the model
and then we notify the CollectionView,
after we changed the model, what we did to the model.
ImageFiles may come and go, they may be changed,
these are the types of updates that we'll need to handle.
We are going to use a little feature,
a foundation feature called Key-ValueObserving,
which basically gives you a way to observe properties
of objects, and be notified automatically,
so that you can then react in whatever way you need to,
our example uses KVO throughout for this.
Let's go back to the demo machine real quick.
I have this already working here.
We'll run our example first, to see what it does.
Instead of looking at the desktop pictures folder,
I'm going to open this vacation pictures folder, on my desktop.
Let's suppose that I have taken some pictures on vacation,
gone to some neat places.
I have Finder windows down here at the bottom,
pointing to desktop pictures, I'm going to copy some stuff
from desktop pictures,
pretending I went to these places.
I'll drag and drop some items into the folder.
CocoaSlideCollection is monitoring the folder,
it will notice the change, and add items to its model,
add ImageFile instances, and then it's going
to notify the CollectionView, okay,
some ImageFiles were added, so display some more items
and that happens in a very synchronized, animated way.
We can drag an item out, a file out, and, when it disappears,
do an updated File system scan, notice the change,
update our model, and the CollectionView
The KVO mechanics of this are pretty standard,
run of the mill.
The interesting, part is how you talk to the CollectionView.
So, we'll elide the former, and we'll just look at the latter.
You want to look at these methods at the bottom
of the Window Controller class, Handle Image Files Inserted
At Index Paths, that's just something we defined
for our own use, and this is where we talk
to the image CollectionView and use this Insert Items
at Index Paths API, basically what we have had
to do is figure out, OK, what are the index paths of the items
that are affected by this?
Where did we insert items?
We messaged the CollectionView, and since we're messaging
through the animator, we'll get an animated change,
where the re-layout that needs to happen,
happens in a smooth way, instead of instantaneously.
You can make it instantaneous if you want, by omitting this.
If you want an animated response,
message through the animator,
you can even control the duration
by setting the animation context duration.
Similar thing for when ImageFiles go,
use Delete Items At Index Paths.
Then there are even sections, InsertSections, DeleteSections,
and other related APIs,
for dealing with sections coming and going.
We're even equipped to handle the new tags being added
and tags being removed.
That's pretty much all there is to it.
So selection and highlighting are important
when interacting with users.
We'll look at those in some detail.
and highlighting are both visually indicated states.
Highlighting in particular is sort of a transient state
on the way to items becoming selected, or deselected,
or used as a drop target.
Here in this illustration we have items
that were briefly flashed orange as I was dragging over them,
they were candidates for selection, we're indicating
that with the orange border, but then they become blue
when they become selected, rather than highlighted.
So on OS X an item has a highlightState.
This is a little different than on iOS,
there is a just a Boolean highlight property,
we needed a bit more flexibility on the desktop to be able
to describe different kinds of states.
The highlightState has four possible values.
The default is none, basically means don't highlight this item.
You'll want to look at whether the item is selected or not,
to decide how to present it.
If it is not selected or highlighted,
you may display it normally.
an item may be highlighted
for selection meaning it is not currently selected,
but we're considering selecting it, based on something
that the user is doing, such as dragging across items.
Then you may want to present it with some kind
of highlight indication, this is entirely up to you how you want
to design this in your UI, we're using an orange border
around the slide to show it is highlighted
for selection, but not yet selected.
An item can also be highlighted for deselection.
This is possible with the shift drag behavior that's the same
as in Finder icon views.
Basically the trick here is the item is selected, but you want
to suppress showing the usual selected appearance.
You want to show something different,
to indicate to the user that the item was selected,
but we're looking at making it deselected now.
You might want to show it normally, this is really
up to you what you want to do, according to your new iDesign.
Lastly, an item can be highlighted
to indicate it is a potential Drop Target
which doesn't make a lot of sense in our example today,
because a slide is the leaf node,
we don't really have a semantic
for dropping slides onto another slide.
But, if we had something that was more of a container,
it might make sense, and we'd want to indicate
that that container is where things are going to get dropped
if the user lets the mouse up at that current point.
Those are the different highlight states.
One handy thing to remember, as I said,
everything is layer-backed now,
with the new CollectionView implementation.
That gives you the opportunity to take advantage
of backing-layer properties as an easy way
to change the appearance of an item
without having to do redraw.
So, CN layer properties such as background color, border color,
border width, corner radius, you've probably worked
with these before, are real handy for this.
So we might set an item's root views layer's background color
to some color, and then give it a corner radius, and boom,
in two lines of code we've got a quick highlight indication
or selection indication, nice and easy.
You don't have to do it that way.
That's just optional, something to keep
in mind now they're we're in a layer-backed world.
When to apply highlighting?
Any time your item's highlightState changes,
in Swift you can do that, in a DidSetObserver clause here,
you will also want to do the same for watching
when the items selected state, the Boolean, changes to yes
or no, you want to take the highlightState,
and the selected state, into account together,
and decide visually how to present that item to indicate
that according to your UI style.
Selection of course is what we're working toward,
we want users to be able to select items
so that they can then operate on them by dragging,
or with menu commands.
With a CollectionView, items are the things
that constitute the selection,
they're what can become selected.
NSCollectionView supports single
or multiple selection, as before.
The master switch is whether it is selectable or not.
If you make it selectable,
you can make it allow multiple selection,
or force just single selection, or no selection,
and you can deny the ability
to have an empty selection making the CollectionView try
to always maintain at least one item selected.
These are pretty standard, they are common to other types
of AppKit CollectionViews and controls
such as Table View, Outline View.
Selection is tracked by the new Selection Index Path Property
That's the authoritative representation
of what's selected in the CollectionView,
and we are using index paths rather than the items, right?
Because items come and go, but the index paths stick around.
An item, if it happens to be instantiated,
does know whether it is part of the selection
as I have mentioned, but again, items come and go.
CollectionViews are forever, so usually you want to look
at the SelectionIndexPaths at the CollectionView,
to do your operations, and there are Select Items At Index Paths,
and Deselect Items At Index Paths, methods that you can use,
you can also just set selection index paths directly.
When you select items at index paths as on iOS,
you can also ask the CollectionView
to scroll those items into view with a particular alignment.
If you want.
User selection is what we're usually dealing with.
The delegate has the opportunity as on iOS
to approve selection and deselection.
We made the API a little different, because again we want
to be able to handle bulk operations a little
Now we have CollectionView, Should Select Items
At Index Paths, and CollectionView,
Should Deselect Items At Index Paths.
Each of which takes a set of index paths as the parameter.
These are the proposed index paths we are going
to to select or deselect.
Notice that, instead of returning a Boolean,
these return also, a set of index paths.
So, if you just want to say, do whatever you want,
CollectionView, just return the set of index paths we gave you,
but you also have the opportunity here,
to return a different set of index paths,
you can do a line item detail here if you want,
based on whatever criteria you want,
you have fine-grain control over which items can become selected
or deselected in certain situations.
There are also DidSelect and DidDeselect delegate methods,
so you can find out after the fact
when the selection change has been committed.
Similarly for highlighting, the delegate has methods
for approving and reacting to these changes.
So, Should Change Items At Index Paths to highlightState, again,
you can return a different set of index paths,
you have fine-grain control
over highlighting behavior with your delegate.
We'll look at this real quickly again on the demo machine.
Fortunately, for time's sake,
I have the code all written and running.
We just want to go into our nib file,
and drill down to our CollectionView here.
We'll make sure that it is marked as selectable,
we'll allow empty selection,
and we'll allow multiple selection too.
The rest of the implementation is pretty straightforward,
based on the understanding that we now have.
We'll stop, build, and run.
Now we can click on items and select them.
We have chosen, for illustration purposes, to show items
that are candidates for selection.
They're highlighted in orange before they become selected,
when I let up on the Trackpad, it becomes blue, it's selected,
and no longer highlighted, we can click in the background
to clear the selection, I can click and drag across items,
again we are showing items as highlighted to become selected,
they're not selected yet, but when I let up on the Trackpad,
they cease to be highlighted, now they're selected.
As I mentioned as in the Finder icon view,
if you hold down shift, and drag-select you actually end
up sort of inverting the selection.
Here is an example of items that were selected,
that become highlighted for deselection.
So, even though they're selected,
we're letting the highlightState override that,
and how we visually present them,
and we're just showing them in an ordinary fashion,
with no border around them.
Then, when I let go, the selection is committed.
Now, since I can select, I can Drag-and-Drop things around,
reorder them which is nice.
Once you have selection there is a lot
of neat stuff that you can do.
Since this is all implemented
in a very generalized way that's agnostic in different layouts,
we can go look at our custom layouts,
since they implement the required methods,
we can also drag-select across items in our custom layouts,
click select, and that happens automatically,
because they conform
to the standard NSCollectionView Layout API.
That's kind of a nice thing to get for free.
Even when we're in section mode here,
Flow layout lets us drag-select across sections, and so forth.
That's kind of neat.
It just works.
Two more things to talk about.
We'll talk real quick about Drag-and-Drop,
which is important to support.
It hasn't fundamentally changed
since the old CollectionView API,
but there are some new things to understand.
We can drag-select items now, and then if you have a cluster
of items selected, or just a single,
you can drag it, move it around.
The CollectionView, as you're dragging,
computes candidate targets for where to drop.
In the case of this example, we're not allowing dropping
on items because they don't represent containers,
but we are allowing dropping between items,
which is the new thing that we have to be concerned
with on OS X and not on iOS.
So drag and drop, as before,
is handled by the NSCollectionView's delegate,
it's responsible for your drag-and-drop response.
The model is intentionally very similar to NSOutlineView's API,
there is no fundamental reason for it to be very different.
If you've seen the drag-and-drop outline view example,
a lot of the same concepts that you'll see implemented there,
[it's] basically the same idea with NSCollectionView.
If you want your CollectionView to be a dragging source,
meaning that items can be dragged out of it,
your basic responsibility is to be able to put items
on the pasteboard when requested by the CollectionView.
If you want to be a dragging destination,
if you want to receive drops, you need to be able
to assess a proposed drop, CollectionView will call you,
say I want -- I'm proposing to drop these objects
from the pasteboard onto this target position,
which will be an index path, indicating either a gap
between items, before an item that's named, or a position
on top of an existing item,
if you're letting it act like a container.
There will be an operation,
these are the standard drag operations,
like copy, move, et cetera.
You can look at this proposal, you can optionally override any
of these parameters, say no, I would like to propose
that instead, you actually target this position
for drop, or refuse the drop.
You need to be able to implement the drop acceptance,
which is very similar, but then the user has committed
to the drop, and you need to go through,
and look at modifying your model accordingly,
and updating the CollectionView accordingly.
The mechanics of this boil down to these APIs, you need to,
like any other NSView, you want to register for the drag types
that you want to be able to accept,
because collection view generically doesn't know what
types of objects you deal with in terms of your model.
CollectionView has a Dragging Source Operation Mask,
both for local and non-local drags.
This is just basically letting you set in advance,
I support copy and move but not alias, or something like that.
You want to set that up.
We do that in our example as you will see.
Then the required delegate methods that correspond
to the responsibilities I mentioned on the previous slide.
Again, you need to be able to write items to the pasteboard,
in the modern API you can provide a pasteboard writer
for an item in an index path.
That lets you deal
with multi-item drags much more gracefully.
Certain data types are pasteboard writers.
In this example, NSURL, if it is an absolute URL,
you can just return the URL as a pasteboard writer,
and it knows how to write itself to the pasteboard.
Alternatively, you can implement Write Items At Index Paths,
toPasteboard, either way, you're covered.
Now, to be a dragging destination, again,
there's a Validate Drop Delegate method,
and an Accept Drop Delegate method,
to abbreviate them a little bit there.
And you'll see those implemented in our code sample.
We don't have time to walk through the code sample
in detail today, because drag and drop is fairly involved.
It's designed to be, enabling you to drag items
from one application to another.
There is a lot to it, but there are some fundamental tips
Once you get these concepts, the rest is just mechanics,
and you'll be able to see it all
in the heavily-commented code for our sample today.
The important things to remember, it is worth figuring
out and especially handling the case where a drag is happening
within your CollectionView.
When you start to get dragging destination delegate messages,
it is worth being able to say, hey,
I know that this drag originated within myself,
I know which items, which index paths, are being dragged
so I can handle this a lot more simply than, sort of,
the general, oh, this drag can be coming from anywhere
in the system, I have to pull things off the pasteboard,
and so on.
This lets you, with CollectionView,
it lets you tell the CollectionView
that you're just moving items from these index paths,
to these new index paths,
and can give you a nice slick animation
as a side benefit of that.
It's a lot better, more sophisticated
than removing the items and then, oh,
I have to reinsert these same items somewhere else,
and reconstitute them.
A handy place to do this is in the CollectionView,
dragging Session Will Begin At Point For Items
At Index Paths delegate method.
It's an optional method, but it's a good place
to catch those index paths, stash them in a private property
of your data source, so you can find that later, and say, aha,
I can handle this much more simply, and you will see
where the code sample does that.
I wanted to leave time to look at customizing layout.
That's more interesting and fun.
Let's go to that.
It's our last task, we're going to look at both what you need
to do to adjust an existing layout,
let's say Flow does almost what you want, but you want
to tweak it just a bit to get everything pixel perfect,
you know, I have heard of doing that before!
Or, maybe you want to implement a completely custom new type
of layout as we have done here,
with our various other arrangements of slides.
We'll look at what it takes to do that too.
Adjusting an existing layout takes a little less work.
We'll look at that first.
Let's say you want to subclass the flow layout class
to adjust item positioning just a little bit,
tweak things here and there.
You can do that with a delegate, but let's just suppose
that you want to do something that you find you can't do,
with the existing delegate API.
This is the main workhorse method to understand.
So far this is the same as on iOS.
Layout Attributes For Elements In Rect is a very general API.
CollectionView calls in,
and it passes you a rectangle that's a rectangle
in the CollectionViews internal bounds coordinate system.
It's basically saying, hey, what's in this rectangle?
You're obliged to return an array
of layout attributes objects.
Remember, that's our encapsulation of descriptions
of items, independent of having
to actually instantiate the items just yet.
You are going to return it information about items,
and if you have header and footer views,
or other supplementary views that could be in that area,
you have to figure out what's there,
and return those descriptions.
This is obviously highly dependent
on what your layout algorithm is.
It could be anything, right?
So you are going to traverse your own internal data
structures, you want to figure out how
to do this really efficiently for your layout.
That is this methods' responsibility,
to return descriptions of everything in a rectangle.
That's the workhorse, that's what gets called,
when the CollectionView first lays
out the items you have given it.
Then, there is this companion, Layout Attributes For Item
At Index Path, almost seems superfluous,
but the CollectionView needs to ask about specific items, say,
just describe this item to me,
and that's what you're supposed to do here.
If there is no item at that index path you return nil.
Usually, there is, if it is asking.
This gets invoked when you're doing things like moving items
from one place to another.
So you want to implement that too, and the results need
to be consistent with the first method.
Then there is Invalidate Layout With Context, which is sort
of a general invalidation method that CollectionView will invoke
with a context, that, if you look at its properties,
examine it, it is the same as on iOS,
it describes what's changed.
Items were inserted or removed,
maybe the CollectionView is resizing, any of a number
of things could be happening.
Examining the context properties gives you the opportunity
to just try to be as smart and efficient as you can.
This is sort of a later optimization you might want
to do, after you get your layout just basically working the way
you want it to.
This is your chance to blow away any invalidated state stuff
that is internal state that you track for your layout,
your own description of it when certain changes happen.
So we're just seeing Flow layout,
then let's say we're going
to implement those first two methods,
the layout attributes returning methods, to call up to super,
see what NSCollectionView Flow layout proposes,
we can examine the resultant layout attributes instance,
or array of them, and make whatever tweaks we want to,
and return a new array of layout attributes,
or a single array of layout attributes.
That's pretty much what there is to that,
as long as the changes you're making don't change the amount
of space the layout needs.
What if you want to implement a completely custom layout,
like we have done here?
You can subclass NSCollectionView layout
directly, to just do everything from scratch,
if your layout has nothing in common with Flow, for example.
You implement the same methods that we described
on the previous slide.
In addition, you need to be able
to answer certain basic questions, like, what's the size
that you need, the width and height, to display the items
that the CollectionView has to offer?
You basically just telling the CollectionView here what's the
size of my document view within?
-- this determines your scrollable area.
Should Invalidate Layout For Bounds Change returns a Boolean,
so CollectionView is going to invoke this
when it's being resized.
And typically you'll look at, What's my layout algorithm?
Is my layout affected by this resize?
If you're a Flow layout for example, a vertical one
that lays things out into rows, maybe you don't care so much
if the CollectionViews height is changing, right?
That just gives you more or less space.
But,if the width is changing, you may have to reflow.
You may return yes in that case for example.
That's what that method does.
If you're modifying the flow layout so that the amount
of space you need changes, you may actually need
to implement those two, even for a slightly customized flow.
These methods, however, are brand-new on OS X,
I mentioned we have the ability now to hit test,
to have a layout in the abstract,
hit test for drop targets.
That's a powerful new feature.
You can define this for any of your custom layouts,
Layout Attribute For Drop Target At Point is the first method.
If the target is an item, that's pretty straightforward.
You're going to return an attribute,
a layout attributes instance,
whose represented element category is Item.
You're proposing dropping on an item,
you plug the index path in, of the item you have identified
that would be dropped onto, and then you want
to return the bounding box of that item as the frame
of the layout attribute.
That's imple enough.
A more interesting case, now, that we didn't have to deal
with on iOS, is gaps between items.
If you determine that the point that's being hit tested is
between items, and you can identify, in some sort
of serial order of the items, where that gap is, OK,
t is between item at index 6 and index 7, you may want
to return an Inter Item Gap.
That will let users drop between your items in your layout.
You return one whose element category is Inter Item Gap,
the attribute's index path is the index path
of the item after the gap.
If you're between 6 and 7, you return the index path
that specifies item 7 in that section.
Then again, you return as the attributes frame, a bounding box
of that gap, the CollectionView will use that bounding box
to figure out how to draw its standard indicators somewhere
in that rectangle.
Next there is a method called Layout Attributes
For Inter Item Gap Before Index Path.
CollectionView sometimes needs this too, and it will ask
about a particular position, and ask you to describe that gap.
So here we return an Attributes
With Element Category Inter Item Gap,
it's represented element kind is Inter Item Gap Indicator,
so this is really, we're using this to set
up a supplementary view.
That's how the Inter Item Gap Indicator is implemented,
you just plug in the index path that you are given,
and your return is the attributes frames
as the Rect of the gap.
With these two methods together,
CollectionView can support drop target indication between items,
even for potentially-arbitrary custom layouts
of your own design, which is pretty neat.
We'll look briefly at our custom layouts,
and how they're implemented as our last demo.
Looking here on the left-sidebar,
we have the code categorized.
We have a layouts group, you want to look in there.
We'll look at one example today.
The circular layout.
It is fairly simple.
We implement Layout Attributes For An Item At Index Path,
that's where we're asked about a particular item.
All we're doing here in concept, is we're taking the item index
from the index path, and that's going
to define how far we are around the circle.
We use that to compute an angle from 0 to 2 Pi radians.
That lets us compute a frame for where the slide should go.
Then the important part is here, the API is a little touchy
to how you instantiate layout attributes instances,
to get one that's bound to the index path
that it references correctly,
you want to be careful to do it this way.
We'll actually talk to the layouts class,
and get the corresponding layout attributes class,
basically having this API allows for layout attributes
for the NSCollectionView Layout Attributes Class
to be subclassed and extended.
You may have some really custom layout, just as on iOS,
that needs to work with other attributes
or remember other things about items that it's laid out,
you can add those properties by subclassing.
You override layout attribute class
to return your own subclass.
This way we make sure we're instantiating a subclass
if we need to.
The appropriate one.
We invoke this factory method, Layout Attributes For Items
With Index Path, we pass in that path we were given.
Once we have got a layout attributes object back,
we set the properties we want to, we set the frame,
the Z index for back-to-front sort order in the layer world,
and then we return
that attributes instance back to CollectionView.
We have a superclass, where for all
of our custom layouts we have implemented the Rect-taking
method, Layout Attributes For Elements In Rect,
and we can do that, because in this case, what's special
about all of these layouts, in this case is rather
than being scrollable layouts, that just grow
to whatever size they need to display their items,
these all choose to display all
of the CollectionViews items in the visible area.
So the implementation of this method is basically always
We are looking at the Rect, and we are going to look
at every item we have.
We're returning descriptions of those back to CollectionView,
so we're actually just leveraging the Layout Attributes
For Item At Index Path method,
that we implement in the subclass.
CollectionView Content Size is the same for all
of these slide layouts, we're just looking
at the clipped view's bound size, what area do we have,
that's visible to the user?
We're going to lay out everything out within there,
and because we're doing that, we're also going
to invalidate layout when the bounds change,
regardless of what the change is, we want to re-layout,
so that we can use the available space appropriately.
Prepare Layout, as on iOS, this is just a handy little hook
for when layout parameters have changed, and you're being called
at the start of a new layout cycle,
you can do any pre-computation you want to in there,
and just gets invoked once, at the start of the cycle.
We can look at our layouts in action here.
And say for the circular layout as we resize,
the layout is getting invalidated each time,
because we want to make the biggest circle we can,
leaving some margins within the available area.
By implementing those relatively-few required methods,
we have a completely custom layout.
And again, as before,
it supports crossing selection here, and click selection,
and it is very versatile.
So, it doesn't take much to define your own layouts for use
within NSCollectionView, and in fact the layout classes,
the APIs you will find, except for those additions,
those augmentations we made to support drop target hit-testing,
basically the API is the same as iOS,
so if you have the layouts you have used on iOS,
you should find it very easy to port those to OS X.
We have covered a lot of topics here today,
we've basically made an example run, and you have access
to the complete source code to that.
I encourage you to study it,
it should help you get started using the new NSCollectionView
on El Capitan.
In conclusion, we have got a greatly enhanced
NSCollectionView, I hope you'll agree on El Capitan,
it is ready now to handle scalability to large numbers
of items, flexibility to arbitrary layouts,
and all of the toughest projects you may want to throw at it.
We encourage you to do so.
Let us know what works.
Let us know what you have challenges with.
If you need any help or guidance,
we have a lab dedicated
to CollectionView specifically tomorrow morning
in Foundation Lab B downstairs, Frameworks Lab B, sorry,
at the foundation of the building, 9:00 a.m. tomorrow.
I'll be there along with other engineers from our team
who understand CollectionView.
Be sure to look at not just the documentation,
but also the Application Kit Release Notes,
I have personally put notes about CollectionView use
in there, details about how to set them up, and also,
you will find information
about all the other great new stuff we have
in AppKit in 10.11.
If you missed What's New in Cocoa, another great place
to find out about all the new features that we have added,
it is quite a lot, it didn't even fit in one talk,
I encourage you to check that out,
on the session videos that are available.
Last but not least we have two great auto layout sessions
earlier today, if you're using Auto Layout constraints
to position your controls within your items,
it might be really helpful to understand Auto Layout in depth.
Thank you very much for coming.
I look forward to seeing what you create.
Enjoy the WWDC bash!
I'll see you tomorrow morning in the lab.
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.