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 savers available.
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, developer-definable layouts.
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 for CollectionView 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 as well.
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, NSCollectionViewItems.
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 of ImageFiles.
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 the user.
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 updates accordingly.
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.
Basically selection 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 on NSCollectionView.
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 more efficiently.
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 to understand.
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 the same.
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.