App Frameworks • OS X • 53:44
Lion now has the ability to use Views instead of Cells inside NSTableView. Get the information on how to quickly develop applications using this new technology. Basic TableView setup will be covered, along with more advanced features, such as insertion animations and dynamically updating content.
Speaker: Corbin Dunn
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it may have transcription errors.
Hello, welcome to Vue Base and its table view from basic to advanced online. My name's Corbin Dunn, I'm a Cocoa software engineer. So what are we gonna talk about today? Well, new online, NS TableView now supports NSView. You don't have to use NSCell. So it's much easier, you don't have to do any subclassing in NSCell to do hit testing, drawing, whatever.
Now, why is this great? Well, you can do easy design time layout inside of Interface Builder and just do it all by adding one view, another view, another subview. You can animate stuff because we can move views around really easily. You can customize drawing by just subclassing NSView and adding other things that you need. You can do other animations inside of the cell, which were difficult to do before.
So what are we gonna talk about? We're gonna talk about layout, which is general NS table view and how the new NS views fit into it. Talk about construction, which is how to go ahead and actually create a view-based table view. I'm gonna talk about bindings, so how to use bindings with this new technology. I'm gonna discuss some customizing, so how to do subclassing and add some cool new features to it. I'm gonna talk about drag and drop and doing some new multi-image dragging with the tables. And then finally, we'll talk a little bit about animating.
Now, when I say the word cell, I'm just gonna mean a particular row column. If I mean NSL, I'll go ahead and say NSL. So I just wanna set that out there so you understand. So let's just jump right into it and do a demo. And this is called the TableView Playground Demo. You can go ahead and download it on developer.apple.com and play around with it. And I'm going to show a few of the features of the UBase TableView. So right here, this window is a basic UBase TableView. It just creates the other windows for the application to show pieces of the UI. And it's completed or created complete with bindings. So there's actually no code really to do any of this. So I'm gonna go ahead and hide that window. So here's a basic view-based table view. It looks like any other NS table view that you have. It has an image and cell, it does typical selection, and it's really easy to create. The basics here is that it looks just like any other table. So let's take a look at a more advanced table view. This is the complex table view example inside the demo.
And what you see here is as things scroll in, views animate in, which was difficult to do before. Up at the top, the headers can flow over content, something really easy to do now. We can do animations, so I can go ahead and select a row and delete it. We can do insertions. We can do moves, so I can move from one row to another. A real easy API to do this. We can do things like animating the row height. and while the row height's animating, we can animate the views inside of it. It's really easy to do.
And of course, Innis Outline View is just a subclass of Innis Table View, so it has all these features too. It does a cool animation throughout the OS when things open and collapse. And a neat thing about this table view is we can do some cool insert animations. So I create drag images, and as I let go, the little gap opens up and the rows just slide on into place. I also show how to do reordering inside of it in an animated fashion. So those are a few of the things that I'm gonna highlight and talk about.
So let's go ahead and talk about layout. Now, in this table view layout, so here's a table in that sample application. Typical layout, we have an NSScrollView at the top, and the scroll view is the thing that lets you do the scrolling. The next layer of the subview hierarchy is an NSClipView, and this is what actually clips the content. Then inside of there, you have your NSTableView, which actually might be a lot bigger than what you see there, and the ClipView is what actually does the clipping. Now, all of that's pretty much the same for what we already had with the NSCellBaseTableView. So let's take a look at what's actually new here. And the next level of a subview that's new is an NSTableRowView. So let's take a look at NS Table Row View and see what it does for you.
So the row view is responsible for drawing selection. And the table has a few selection styles, and the row view has some properties so you can access and see that selection style. So here's in his table view selection highlight style regular. And of course we have a source list highlight style. So in his table view selection highlight style source list.
The row view is also responsible for drawing the background color. So you could set the row view dot background color to be a red color and that row will draw red or whatever other color you want. Of course, the red looks kind of ugly, but this is just an example. It can make it green or whatever else you want. It's really easy to do.
The row view is also responsible for drawing the group row background. So the group row background we saw in that outline view demo, there's a whole row with the little gray to kind of separate a group. This is done by you implementing the delegate method, table view is group row, or the equivalent outline view version, and saying yes for which rows are group rows. These give you that group row style, and if that's set, group row style will be yes, and it can draw differently.
In addition, there's a new property on table view called floats group rows, so all those group rows can automatically float over content optionally. And if that's set, the row view has a floating property which would be set to yes. And then you as a subclasser could look at that property and draw differently if you want.
Now, the row view also draws the separators between one row and another. And the reason it does this is it allows the separator to move around with the table, or sorry, to move around with the row when it animates. So the separator is specified by the grid style mask on the table view.
And there's a couple options. There's the NSTableViewSolidHorizontalGridLineMask. It's really hard to see it in this screenshot. There's also a new one in Lion that's a dashed option. Again, it's really hard to see the dash, but you can look closer in the demo app and you can see it.
The row view is also responsible for drawing drag and drop feedback. So when you drag over that row, you want the whole row to show some type of feedback to the user. And so there is dragging destination styles that the table view has and draws for you. There's in this table view dragging destination feedback style regular, which is just the regular feedback style. And there's also a source list style that has a different look for source lists or sidebars or whatnot.
The row view computes a few properties, and one of them is the interior background style. So why is this important? Well, depending on what properties the table set on the row view, like if it's selected or whatnot, the row view will calculate what its background should be. So in this case, the interior background style is set to and it's background style dark. So that lets you know that, hey, all my content should probably be light text because I'm on a dark background. And then of course there's in its background style light, if it's actually a light background, and you should probably have dark text to stand out against it. There are a few more in its background style options that I encourage you to look at, which might be set. These are the two basic ones that are really important to know.
Now, that was the one thing that the table row view computed. It doesn't compute all the other properties, the selection and whatnot. They are set by the table view. You as a developer could use the new delegate method. Table view did add row view for row to go ahead and override these properties to be whatever you want.
Let's take a look at NSTableColumn and how it works with NSTableRowView. Well, the row view stores a number of columns, and there is a view per column. So if we look at this table here, the outline view example, you can see there are three columns. There's one, two, three. And if we take a look at one individual row view, it has all three columns inside of it, so it stores its number of columns. And if we take a zoom up of it and look at it a little bit closer, you can see it has one view per column. So it stores that. And that view there could be any NSView. This first one is just a regular NSView with just regular NSView, or regular subviews, an image, an NSTextField. You can put whatever you want, it doesn't have to be a special subclass of anything, and so this is just a regular in this text field. This third one is a custom NSView subview that does custom drawing, and then another regular NSText field.
Now, you could use any view you want, but we highly encourage you to use Innis Table Cell View. It's optional, but it gives you some real cool and easy things to do. Specifically, it gives you a couple of outlets. It gives you a text field and image view outlet. So you as a developer could set this text field outlet to your text field, and the image view outlet to your image view.
These are easy to access in code, but they also do a couple things for you automatically. They're hints for accessibility, for one. So when a user is using VoiceOver and goes to your table view, the text field property will automatically be read off to the user without you having to do any work. It kind of specifies the main text for that row.
Now how many views do you have? So you have a view-based table view, and if you have 20,000 rows or something, does that mean you're gonna have 20,000 row views and sub-views within it? Well, no you won't. What you'll have is just a view for each visible, or a view in the visible rect, and that is it. There are no extra views outside of that visible rect. Now there are actually some caveats to this. For instance, if the first responder is scrolled off, the table actually will keep track of that. Or what if you had views animating around? The table will actually keep track of those views so that you can update them dynamically if you need to. And I'll show you how to do that in a short bit.
So what happens when you scroll? So you scroll up, there are no views there, and so the table view is going to just go ahead and pull those couple of views that are no longer visible and toss them into a reuse queue. Now, you might have something else already in this reuse queue, and you can just go ahead and pull those other views out of the reuse queue and reuse them. So it's good for performance to just reuse the views again and again.
So that's basic table view layout. Let's move on and talk a little bit about construction to figure out how you make a view-based table view. So the basic data source methods haven't changed. You still have to implement number of rows in table view. And the caveat to this is if you use bindings. So what you'll do is you'll just return your model count for the number of rows you want. An optional method is table view object value for table column row. Previously this was required with the NSL based because it was set to the NSL value. But now it's optional and I'll explain how you can use it in a little bit.
So, now what can you do in your delegate is the thing that actually tells the table it's a view-based table view. You need to implement table view, view for table column row. And here's one of the most basic implementations. You would alloc init your view, in this case a simple text field. Now, in this case I'm not showing a frame that you alloc with, and the frame really doesn't matter. The table controls the frame. You don't need to specify it. It will use the frame for whatever that cell be.
So you knit your view, set the string value or whatever properties you want to it that are specific to that particular row, and then you can just go ahead and return an auto-release view. So it's really quite simple to do. Of course, all this is optional if you use bindings, and I'll talk about how to do that in a little bit.
So let's talk about identifiers. So when we look at this complex outline view demo in the TableView Playground demo app, we have a bunch of homogeneous cells. So all these would be a main cell, is what we're gonna give it as an identifier. And then that next column, we could call it a date cell. It's just gonna be a plain NSText field. Then that third column could be a color cell. And finally, down at that bottom, we have the group cell. It's another way of identifying that class of cell.
So how do we do this? Well, NSView now has an identifier property to uniquely identify that type of view. And here's how we do it. There's a new protocol, and it's user interface item identification, which gives an identifier property. And this view implements this protocol, and now you have an identifier that can be set or read.
So here's a more typical implementation of a view-based table view. And if you've done any iOS programming with UI table view, it's quite similar to that. You're gonna call a new table method, makeViewWithIdentifier, passing in the type of identifier that you wanna create, and an owner of yourself.
Now, if it didn't have one from the reuse queue, which is what that method would do, you're gonna get back nil. And so you'll have to allocate a new view that you wanna create for that cell, set the identifier, then go ahead and set up your string value and other properties on it, and return that result. So this is a very typical manual implementation. But we thought about some more and thought, hey, we can make this even easier. So this is an even more typical delegate implementation. Inside of it, you're just gonna call makeViewWithIdentifier, and it's gonna always return a result for you. And I'll show how in just a second. Of course, you still have to set up your initial properties here, like the string value for that particular row.
So before I talk about how that method works, let's talk a little bit about NSNib loading and how that works. NSNib has a method instantiateNibWithOwner top level objects. Now what's important about here is the owner property. So this is just a screenshot of Interface Builder inside of Xcode. And the owner property there is that files owner that you go ahead and specify. Files owner is important because that's what you can set your target action to, or your outlets from. So keep that in mind.
Here's a screenshot of how we do design time layout of the view-based table view. There are three columns. Now what you can think of is each of these design time cells are effectively a little NSNIB that the table view will encode within itself. So we have this first one, which has an identifier of main cell.
Next one is the date cell. The third one would be that color cell. And the final one would be the group cell. So each of those will just be encoded into the nib automatically within the table. So the table has them and knows about them, can replicate everything about that view, including the bindings.
So how does makeViewWithIdentifierOwner work? So you need a view with an identifier called main cell. So you as a developer are gonna call makeViewWithIdentifierOwnerSelf. is that cell with that identifier already in the reuse queue? Well, if it is, it's going to go ahead and return that old cell with that existing identifier that it already found. If it didn't, and here's the new part that's a little different from iOS, we see if there's a design time view with that identifier. If there is, the table automatically calls instantiate_nib_with_owner, finds that view with that identifier, and goes ahead and returns you that new instance of that view. If there isn't, you're kind of out of luck, and at this point it does return nil. But that's how that method works.
Now, updating view state for a particular cell. So if you set some properties, like you set the font to be bold or something specific, you as a developer will probably have to initialize it in this method. Otherwise, if you got one out of the reuse queue, it might have whatever properties were previously set on it. So it's just an important thing to note that you have to reset all things like the font. So let's take a look at another demo and just see how basic table view construction is done at design time.
So this is a really basic view-based table view. And I want to show how you would add a new cell view. Inside the object library, you could just search for NSTableCellView. And you could drag an image and text cell that's already predefined and set up. And so the new important part is the user interface I'm identifier, and you could call it my cell or whatever you want.
So I'm gonna undo that and just look at one that I already dropped down here. If I select the cell view, I can see that this one has an identifier main cell. All right, so that's how it's actually set up. Some other interesting things about it, You'll recall previously I said that it had two outlets from Inno's Table Cell view. It has an image view and a text field outlet.
So I'll show you how those come into play in a second. In addition, you can drop down other views like a button inside of it. And you could actually have the buttons action set up to your file's owner to do like a button clicked action here. So I'm gonna show how you would actually respond to that button inside the cell view. So let's take a look at the code for an implementation of this.
So in a basic implementation, you would go ahead and create your model object. Here it's just an array of images. Number of rows in table view, of course, is just going to return the array count. Then the interesting new part, view for table column is implemented. And as I showed you before, at design time, there is a cell with an identifier called main cell. So make view with identifier will always get a result back created from you, either from the reuse queue or a new one that it just instantiated. It's going to go ahead and grab the model object and then set up properties on it, accessing those outlets. So it accesses the text field, set string value, and accesses the image and sets image value, and then returns the result. So that's the most basic implementation of a view-based table view. It's really easy to do. Now how do you respond to an action? So if you click on that button, well, this is my delegate and there are a bunch of rows.
How do I know which row that action should be applied on? Well, the table view has a method called rowForView. So you can just find out what row that view is actually on. In this case, we're gonna find out when the sender was on, and the sender was the button. So we know which row was actually clicked on, and we could do some action based on that row. So that's also pretty easy to do.
So how do you do editing? That's the next question that people will usually ask. Well, within a cell you would use TableView, set object value for table column row. Now, if you have a view-based TableView, what if you have more than one text field in that cell? It's kind of ambiguous how you would actually, or what the table should do with this method, 'cause we don't know which one you want to edit. And actually, we don't have to do anything special. It's just a text field. You can just use normal target action-based editing or notifications on that text field to find out when text changes. And so that's what you're gonna do, and it's really easy to do.
How do you manually begin editing? Well, with NSLs, you would use edit column row with event select, and that would go ahead and edit that cell. But again, it's ambiguous if you have more than one text field. And in fact, editing is really just making it the first responder. So in a view-based table view, you'll just call make first responder, and then it begins editing. So that's really easy too.
But how do we control editing to make it behave like a table? Normally in Finder, if you click on a row, it doesn't start editing right away. In fact, you can only edit a row in Finder if you click on text in an already selected row. And it actually happens in a slight delay, so you can actually do a double click and have a double click action take effect, or a single click action to begin editing. So how does the table make that work? The table overrides hit test and doesn't let the hit test go to the view, and if certain conditions are met. And the way it does it, is it calls a new responder method on itself, validate proposed first responder for event. So it takes that view that you clicked on, and calls it with that as the responder, passes the event that was coming in, and the table then implements this and says, okay, is this something with text that needs to be editing? Yes it is. well, I'm going to save off what would be the first responder and just wait until the double-click interval passes. After the double-click interval passes, the table just calls makeFirstResponder on it and it starts to edit. This way, a double-click action could fire and it will stop that.
Now why is this important? We use a developer could subclass and override this and add your own behavior. Or you could have a control which is in a table that might want to do something only if the table says it can do it. So you inside your control could call validate propose first responder up the responder chain and the table, if it's there, could do certain things that prevent editing or prevent like the IBM cursor from showing up.
All right, so that's basic construction of a view-based table view. Let's move on and talk a little bit about bindings and how they work. So how do you bind content for a particular cell? You can still use an NSArrayController, except you will use the NSArrayController only to bind the content of the table view. You will not use an NSTableColumn and set up properties on that, because that only applies to the NSCell in the table column. Now here is where you could implement that table view object value for table column row method and actually return something. So if you don't want to use an array controller for your object, but you still want to use bindings, you implement this delegate method, and you can return your model object from here. So let's see if you do that, how it would actually work.
Now let's take a look at a simple object value type of view. So right here, this is our view which we gave an identifier of date cell. And what it is, is it's just a basic NSTextField. There's nothing fancy about it. NSTextField implements set object value. So what the table does, it says, hey view there, do you respond to set object value? You do, okay. Well I'm gonna take whatever object value I have, either from that delegate method that you implemented, or from the array controller, and just call set object value on you. Now, this is pretty much what regular NSL-based table views were doing for the longest time. Nothing really special about here.
So let's take a look at a complex object value example. So this cell here, which we would call the main cell, it has more than one property that we want to bind to. Before it was difficult to do more than one binding inside of a cell. So how are we gonna solve that?
The answer is to use NSTableCellView as your whole cell. NSTableCellView also has an object value property. And so again, the table is going to see that it has it there, call it, and set the object value. So that means the whole cell has this object value inside of it. What you can do with that is you can do a binding from that particular view, like the image, to the cell view's object value, and then any model object properties you have, like the image.
Or your text field, you could do cellview objectvalue.title. So that's how you can really easily do bindings inside of it. So let's take a look at how you set them up inside of Xcode 4. So this has got the text field selected inside of design time environment. And the things to point out here is that there's a new binding target that you can select. You can select the cell that that actual view lives in. And if you're binding to that, there's always going to be an object value property. So you can bind to the object value of that title in this case. So that's pretty easy to set up and do.
Now, let me talk about automatic view loading. So way before I said, table view view for table column row is actually optional. So how does that work? Well, we know what table column you're passing and it has an identifier. and we can just automatically use that same identifier to find a view that you designed at design time. So again, in the TableView Playground demo here, if I go ahead and select the table column, the thing that's interesting to point out is that the identifier is set to sample window cell in this case.
Now if I select that cell, and again, you could have multiple columns, or you could have multiple cells. In this case, it's just a one column and one cell. But the interesting thing here is that that cell's identifier exactly matches the table column's identifier. And if it matches, the table view will go ahead and automatically use that cell from the nib, and you don't have to implement that delegate method at all.
Now, you're probably thinking, oh, well, it might suck to keep those two things in sync because now I have to rename it in one spot and rename it in another spot and do a bunch of work. And if one's not set, you might get no views showing. Well, we have a solution Xcode for, and it's called automatic identifiers. So what you can do is when you select the table column or the cell, you just set the user interface identifier to automatic, which is the default value. And what's going to happen here is we will automatically create an identifier for you and keep the two for the table column and the cell in sync automatically. So it makes it easy to do. So let's move on, talk about customizing a view-based table view.
So I'm going to show another demo application called the Hover Table Demo. Again, you can download this on the developer site and play around with the source code. And some interesting things here is when I hover over a particular row, it does a cool hover animation. So I'm going to show how to do that. When I select a row, a couple things happen.
It draws custom selection, and the text field's text turns bold. I'm going to show how to do that. Another thing it's doing, which is a little more subtle, is the horizontal separators are actually a subtle little gradient to kind of give it this cool look. So I'm gonna show how to do that too.
So how do you do custom row selection now? Well, you can just subclass in this table row view to do it. In this table row view has a bunch of hooks to customize drawing. You can override draw selection in Rect, and I'm not gonna really go over the code, but all it's gonna do in this particular case is it's gonna create a Bezier path and fill it with a light gray color and a darker gray stroke. So it's really easy to customize selection.
Now, what was hard before to customize selection with is, how do you know if that table should draw in the active or inactive state? So when the table is first responder, it would usually have a blue highlight, and when it's not, like in a background window or not the first responder, it would draw a selection with a gray. And the way that you can do that is you can look at the row view property emphasize. If emphasize is set to yes, it should draw in the active state, and if it's no, the inactive.
Now how do you do that bold selected text? And so when the selection changes, you get the typical delegate method, table view selection did change. Now what's new here is you as a developer want to enumerate all the views the table knows about. You could try and get all the views in the visible rect and work on those, but I said before, well there might be some extra views floating around with animations, or a view might be the first responder and scrolled away from the visible rect but still around. So you want to enumerate everything the table knows about. So you're going to call enumerateavailableRowViews using block, and you're going to pass a block which takes the row view and the row that the table has and knows about.
So you have the row view, and the row view stores these views, so you can use a view at column to find that particular view for the first column. And before we saw the view had a text field outlet, so we can go ahead and grab the text field outlet, we can access some properties on the row view, like if it's selected or not, and then based on that, well, if it's selected, let's use a bold font, if it's not selected, let's use just a regular system font. So it's pretty darn easy to enumerate all the row views and all the views and update one particular property that you want. It's also quite efficient and fast to do. So how do you do this hover effect when you mouse over and it does this kind of cool little gradient? Let me show how to do that.
What you're gonna do is just use the typical NSView method, update tracking areas to go ahead and update some tracking areas. In this case, it's gonna allocate a tracking area and store it in an INVAR of the row view. If that's not in our tracking areas array, it'll call add tracking area on itself and it has the tracking area set up.
The tracking area will then give the row view a mouse entered and mouse exited event when the mouse comes in and goes. And so all we're gonna do is set a bit under bar mouse inside to know when the mouse is inside of us and let us re-display. And of course, mouse exited, it'll undo that.
So then in our drawing method, again there are lots of great drawing hooks. You can subclass and override draw background and erect. You can fill with that background color that I talked about way long ago. You can check your bit to see if the mouse is inside of you. And if it is, you can alkanit a gradient and just fill with a gradient. And that's how you can do that hover effect really easily.
Now how do you do the custom separators? It's kind of hard to see that separator again, but it's a nice little cool gradient. And what you can do is you can override draw separator and rect. For the row view, you're responsible for drawing the bottom of the separator, or the bottom separator. The previous rows will draw the top separator above you. So you're gonna grab your bounds, you wanna draw at the bottom, so you're gonna get the max y of your separator, or max y of your bounds, set the height to one, and then the interesting thing is you're gonna probably use a common method called draw separator in rect to do the fill of the separator however you want. Now I'm gonna show why in just a second.
So the row view draws its own separator so that it can move around with the row view itself when an animation happens. But how do you deal with these empty separators that are at the bottom of the table view when you have more content to show? Well, the table view already has a method to customize the grid drawing, called drawGrid and clipRect. So here you can override that and do some custom separator drawing.
The code's a little bit more complex, but what you want to do is, you don't need to draw those separators for the spots where all the rows are at. So you grab the max y of the last row. You're gonna go past it, because that last row drew it separate at the bottom. And now the code's gonna scroll up to the next page.
You're gonna do what you did before basically, which is you're gonna figure out the bounds for the separator that you wanna draw in. You're gonna enumerate until you're actually at the bottom of your view, and you're just gonna call that same method before, drawSeparatorInRect, to go ahead and draw a separator. So it's pretty easy to do that too.
So you have this custom row view. How are you gonna actually tell the table about the row view? Well, there's a new delegate method, table view row view for row, which allows you to go ahead and allocate a row view, your custom one, set any custom properties you want, and return the result. Now, another thing to note here, again, the frame isn't really important. You could use the row height if you want, but the table will control the frame, and so you can kind of specify whatever you want there.
Now, you can also do this at design time. You could toss your row view as a subview of the table view at design time, and just use make view with identifier to find your custom row view and create an instance of it. So you don't have to do any creation, and then this uses the reuse queue too.
But even that's optional, so you don't even have to do that, and you can do it all at design time. So here's how you can specify a row view at design time without having to write any code at all. I have the row view selected here, and the thing that is unique and must be pointed out is that there's a special user interface item identifier that is set, NSTableViewRowViewKey. If you use this special identifier, which is in the header, then the table automatically search for and find that row view and use it for all the rows. And then you don't have to write a delegate method or any code to specify it. So let's do another demo of drag and drop in TableView Playground.
So if I go ahead and show the complex outline view example, I wanna show some drag and drop features and talk about them again. So one thing that's important to note when I start dragging, there is two separate rows that were dragged, and I want to show how to create two separate rows. When I let go, it's subtle, but what happens is the table opened up a gap, and then the drag image actually animated from where you had the drag image all the way to that gap's ending location. So I want to show how to do that.
So multi-image dragging. So previously you would have one huge drag image for the table. Now you can have multiple NSDraggingItems. If we take a look at NSDraggingItem, now hopefully you went to Raleigh's talk yesterday. If you didn't, I'm gonna do a quick review on NSDraggingItem. The dragging item has an item which is the pasteboard reader or writer that specifies the pasteboard data. It has a whole frame for that particular item because inside of it you're gonna have an image component, or really you might have more than one image component, in this case there's two.
So that's what an init dragging item is composed of. The API for it, to create one when you put it on the pasteboard, is using init with pasteboard writer. So you provide your pasteboard data for the dragging item, and then you're gonna provide an image components provider. This provides all those image components.
Now it's a array of init dragging image components, and it's a block because you don't wanna provide all the components all at once, because they all may not be visible. You don't want to drag 20,000 rows and create 20,000 images. The dragging system will call you back and create them only when they're needed.
So how do you put an item on the pasteboard? There's a new table view method, PasteboardWriterForRow, and what you're gonna do here is for that particular row, you're just gonna return something that implements NSPasteboardWriting. So typically, you might just make your model object implement NSPasteboardWriting. In this case, in the demo, it's an ATDesktop entity that implements it.
What's gonna happen is the table will automatically take that pasteboard writer, create an inis dragging item for you, and use that to start the drag with. If you don't want that row to be dragged, you can just return nil, and that particular row will be excluded from that dragging. So how do you implement NSPaceboard Writer? Well, it's pretty easy. You will just implement the protocol, and I'll show you in a second what you'll do. But for this particular example, we have a file URL that is the main thing that we're dragging around.
So our implementation will implement the two required methods, writable types for pasteboard, and pasteboard property list for type. And we have that file URL, and this URL implements in this pasteboard writer, so we're just going to delegate those methods directly to our file URL. And that's how we can easily add drag and drop support, or drag source support from the table. Now how do you provide those drag images? Because that NSDRAGGING item has those image components provider to provide a couple image components. So like the image and text is being dragged around.
Each of those is an NSDRAGGINGIMAGE component. And they have a key to identify what type it is, in this case it'd be an icon, and a contents, which is the actual image that's being dragged around. So here we want to drag around two images. We want to have one that has a key of NSDraggingImageComponentIconKey. So this is the main icon that the dragging system will know about. And the other one is a label key to specify the label portion. And that way the dragging system can animate the label portion from one view to the label portion of another view and move it around as necessary.
Now, these can be automatically provided for you by Innis Table CellView. There's a dragging image components method on Innis Table CellView, and what that does is it goes ahead and uses your two properties that you set up, again, the outlets from the text field and image view. If you as a developer set those up, then the dragging image components will automatically provide those for you without having to do any work. Then the question is, well, what if you have another one that you want to provide a image for the color or something, Let's show how you would do that. You can subclass in this table cell view, override dragging image components, call super, which is gonna give you those components for the image and text. They're gonna have to create in this dragging image component with your key that you want. In this case, we could call it color. You're gonna set the contents to be an image that represents your view being dragged. It's pretty easy to do, and inside of the sample application, I show how to do it. You're gonna set the frame with respect to the cell views bounds. So it does a convert rect of the color views bounds from the color view into our own coordinate system. And then you just add it to the result and return it. And so that's how you get that extra drag image to be dragged from your cell view.
Now how do you change a drag image? So in Finder, if you have a big icon view with a big image and text, now when you drag it over the table view, you want to go ahead and morph into something that the table kind of knows about. You want the image to shrink and the text to move over to the right. So how do you do that? Well, you want to be a dragging destination, and what you're gonna implement is a new line method called outline view update dragging items for drag.
Inside of this, you're going to want to take that pasteboard data that's being dragged over you, and create a view, and create dragging image components for it, to automatically animate from what was dragged over you to what, or from the source to what's going to be you and your particular representation. You create an in this table cell view with make view with identifier, and you're just gonna use that to kind of stamp out the images. You also set up a cell frame for your particular row, just setting up your width and height.
Now here's what you're gonna do is create an array of all your model objects that your table can create. In this case, it's the AT Desktop Entity class that I talked about previously. And then you're gonna call a new method on inits dragging info called enumerate dragging items with options for view, classes, search options using block.
I'm going to highlight some of the important parts about it, but you get this block callback for every instance of the model object that could be created. We're gonna look at the dragging item and kind of ignore the index and stop parameters 'cause we aren't gonna do anything special with them.
So with the dragging item, that's that thing that was dragged over you. Well, you want the frame to become the frame for your cell view, so you go ahead and update that. And you want to specify a callback block, essentially, for the image components provider to specify the new image components that are gonna be dragged over you.
And what you do inside of here is you have your sample cell view, you set the dragging item to it as your object value. Now this dragging item, that was automatically created from the classes there. So the classes, which you have specified as your model objects, are how that gets into the item. So the dragging info created an AT Desktop entity for us with the Pasteboard reader automatically.
So we set that as our object value. We have bindings or whatnot to automatically display things. Set up our frame, and then again, the cell view has that dragging image components, and so we're just gonna use that to return the dragging image components, and it gets whatever stuff we added to it, or whatever stuff the table implements automatically for us.
So how do we accept a drop and do that animation that I was talking about? So we want the table to open up the gap and wherever the drag image is located, drag from that location right to our final location. So let's see how to go ahead and do that.
So first, you have to tell the dragging system that, well, you accept the drop, and you want to do an animation. Outline view, validate drop, proposed item, proposed child index is a very old delegate method to accept a drop. So you'll do your typical pasteboard validation, and you can look at the demo application to see how we do it.
We're gonna set a new parameter online called animates to destination on the dragging info. That's telling the drag subsystem that, hey, you do wanna animate and you're gonna do an animation from that source location of the drag image to your final location of where your row is. So that's important to set.
Now, how do you actually accept the drop? You implement the old delegate method, outline view acceptDrop, item, child index, and table view has a similar one. What you do inside of here is what you would do when you were updating the dragging images, except you want to actually accept the drop and put things into your model. So you're going to create another array with your model objects of like AT Desktop Entity in this example. And you're going to call enumerate dragging items with options for view classes, search options using block again.
And let's take a look at your implementation of what this block would look like. So again, that dragging item to item was automatically created from the array that we passed to this method. So it used NSPACEBOARD_READER to automatically create your model object. So we grab our model object and store it in a little local. We take that model object and insert it into our array that we have for our model. In this Outline View example, it's just putting it directly into the children array.
So that updated our model, now we have to update the view. So the outline view has a new method, insert items at indexes in parent within animation. I'm gonna talk a little bit more about animations in a second. And now what this does is you pass an animation of an NS table view animation effect gap, and that's gonna tell the table, "Hey, open up a gap, "because we're gonna do an animation into it." As soon as you call that method, At this point, the table can be thought of in its final state. So you did an insert into the table, the table knows that you did that.
You can call methods on it, like find out the row for that particular item you just inserted, You now have the row and you can find the actual frame of that item using the typical table methods like frame of cell at column row. And what you can do here is you'll just update the dragging frame. So what this does is it's telling that dragging item, hey, drag from wherever you are to this final row location and animate it. So that was how you would do drag and drop and accept it. Let's move on and talk a little bit about animating.
So there are three basic methods to do an animation, and they are very similar to InnsMutableArray. What we have is insert rows at indexes with animation, remove rows at indexes with animation, and move row at index to index. These are the table view methods. There are also equivalent outline view ones, like I was previously showing a use of, that insert into a particular outline item and the children of that outline item. They work in a very similar way, but I'm not going to cover them.
So how does this work? Insert rows at indexes takes an index set, and this is kind of pseudo code, but let's say I pass an index set with a index set, indexes of one and three, and see how it would work. Well, what would happen is a hole would open up for row one, and that item would be inserted.
a hole would open up for row three, and then that item would be inserted. So two new items that we pass in would automatically be inserted. The cool thing to note here is that by using these methods, the selection is automatically updated. So you don't have to worry about the selection being stuck at the wrong row. This is much easier than using something like reload data.
Now what if you want to batch updates? So what if you have a bunch of changes that you are going to do, you're going to insert a ton of rows, remove a ton of rows. Well it's better for performance to batch them together inside of a begin updates and end updates table view block. The reason that you want to do this is, let's say you go ahead and insert 100 rows and you call insert rows of indexes with 100 of them, and then you delete those 100 rows, or you insert another 10 or 20.
Anytime you call that method, the table will go ahead and create views and do an animation. Well, you don't want it to animate those 100 rows which might not be on screen at the end of your particular set of operations because you might be inserting and removing them right away. So if you batch them all together, the table is smart enough to optimize it and do only what the user would end up seeing. So for performance, you want to call begin updates and end updates.
Now, how does this work? Now, it's important to realize this is actually a little bit different than the similar methods on iOS and UITableView. In that case, with UITableView, it would sort of freeze the table's contents and you could kind of modify each row. The one for NSTableView works much more similar to NSMutableArray. So here's an example of what I mean by that. You call begin updates, do insert row at index with an index of 2, Opens up a slot, you get that new first row. You call it again, opens up a slot, you get that new row.
Now this isn't really too complex, it's really just the way an array works. You're inserting it again and again. But this is different than iOS, and if you tried to insert the row twice in iOS, it might throw an exception. So I just want to point out that difference there.
Now how do you do row height animations? So one of the other things I showed before, and this is a little video, is how do you animate the row heights for the table? In addition, the table's animating the row heights, but you own that cell view, and inside that cell view some other things are happening. A couple views are fading away, and the actual contents of the image are shrinking. So how do you go ahead and do that?
What you're gonna do is use note height of rows with indexes changed. So this assumes you're using variable row heights and using the variable row height delegate methods. You create your index set, call this method, and now this method will just always animate. So it always goes ahead and animates for you for every view-based table view.
So how do you take your views and sync them with that animation? Well, the way you're going to do it is use an animation context and do a begin grouping and end grouping to group it all together. So inside of that, you could do things like change the duration of the animation, You can update your views, say do an animation to hide a view or change the size of the image view.
Then you can go ahead and tell the table to do its animation by calling note height of rows with indexes changed, and it will animate, and then you end your grouping. They will all animate then together. Then that begs the question, well, what if I don't want that method to animate? The way you can make any of those methods to animate, such as the insert, remove, and move methods, is you can create an animation grouping and set the duration to zero. So if you set the duration to zero, it will tell the table, "Hey, I really don't want "to animate this thing, so don't do that."
Now, it would be really cool if all these methods work within a cell. And in fact, all these methods will work within a cell-based table view. The insert, the remove, the move, the height of rows with indexes changed will all work. But you have to do one thing. To make them work with the NSL-based table view, you have to call begin updates and end updates. When you call begin updates, what the table view will do, it's gonna draw all of your NSLs into a temporary image and swap them out with a view-based table view with exactly what you were seeing there.
you can then have an initial state to do an animation from. So you can call insertRowsAtImages, removeRowsAtIndexes or whatnot, and the animations will happen and work. This is actually how all the animations work inside a line for outline views, which were not moved to a view-based table view.
And if you want to see an example, the drag and drop outline view demo application was updated. You can go find it on the developer site and see how it's done. So in summary, I talked about layout, how tables are actually constructed, how a row view comes into play, and how it all fits together.
Talked about construction, how to use the data source methods, the delegate methods to actually create a table view, and how to do a design time layout of everything. I talked a bit about how to do bindings and how to bind content in a new way where you have multiple values inside of your cell. Talked about customizing table views, so download that Hover Table demo, check it out, play around with the source. Talked about drag and drop, creating dragging image components and dragging multiple rows instead of a big single drag image. And then finally I talked about animating and how to do some cool animations. So for more information, contact Bill Dudney, our frameworks evangelist. Our documentation has been updated for the view-based table view. We have the developer forums. I frequently browse around there and answer table questions.
We have related sessions. James Dempsey is giving our design patterns to simplify Mac accessibility at 3:15 today. It's also the song talk. So I highly encourage you to go to that. He's gonna talk about how to add accessibility to view-based table views, which is really easy to do now. Before it was difficult to add accessibility to NSLs. Now it's pretty easy to make it with a view.