Application Technologies • 1:06:06
Learn to take full advantage of powerful Cocoa bindings capabilities such as managing an outline view, presenting information from a dictionary, and implementing cross-nib bindings. Gain insight into common development techniques used by the experts.
Speaker: Ron Lue-Sang
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Again, my name is Ron Lu Saing. I work on, among other things, Cocoa bindings. Our agenda today looks like this. We'll start off with a conceptual overview of Cocoa bindings, a really brief overview. Then we'll jump into some details about the most commonly used bindings, things that just about everybody is going to use at some point. After that, I'll introduce some of the things that we've added to Leopard in Cocoa Bindings, and we'll wrap up with some practical information about using the controllers and bindings in Cocoa Bindings. So we'll start off with the basics.
When we talk about a binding, we're really talking about two things. Cocoa bindings is really two things. First, a collection of reasonable controllers, and an API for establishing a binding. And what is a binding? A binding is really a contract between two objects. And the idea is that you want to keep the property of one object in sync with some property in another object.
And if you write GUI applications today, without Cocoa bindings especially, you're probably spending a lot of time doing exactly this, trying to keep some property in your view layer objects in sync with another property in your model objects. The idea behind Cocoa bindings is to provide a set of reusable controllers that manage the model objects that you're dealing with, and also a set of reusable-- so the other thing about the reasonable controllers is managing the notion of a user selection. And that way, when the user changes what's selected, it can also be shown in your view layer objects.
First though, let's take a look at the reusable controllers and Cocoa bindings. Male function. Okay. If you've ever used Cocoa bindings, chances are you've seen these little green boxes in Interface Builder. Everybody who's used Cocoa bindings at some point has probably used Interface Builder to set up their bindings. They've seen controllers as this. Cool little green squares.
And likewise, in order to actually set up a binding, you've probably also used the bindings inspector in Interface Builder. I want to make sure that everyone realizes that even though it makes it really easy to set up bindings and use controllers by using Interface Builder, it's possible to set up all of these bindings and all of these controllers directly in code. There's no magic going on in Interface Builder to do these, to set up bindings. There's one core method called that's bind to object with keypath options. And that's it. That's what's going on behind the scenes in Interface Builder to set up these bindings.
And if you were to look at how to set up a binding for a text field that you're interested in keeping in sync with some user default value, this is the kind of code you would write. You would take your text field instance, bind its value to our user defaults controller, one of our reusable controller classes, and use a key path like values.petname, whatever you want to keep in sync with the user defaults. And that's the call. That's exactly what Interface Builder is doing behind the scenes. So I want to make sure that nobody thinks that there's any special magic. It's all available to everybody.
Because this is just a method, there is work going on behind this method call, right? The object that you send this message to has to do some work. And the main obligation is-- To implement the binding, right? To actually do the work of keeping its value, its whatever property is represented by its binding, in sync with whatever you bind it to and the key path of the thing you bind it to.
And it's also up to that object to interpret any of the options that are passed in in the bind method as the options dictionary that might affect how the synchronization happens. So a lot of the options that you might see in Interface Builder really come along as just dictionaries, and it's up to whatever object implements this bind method to interpret how these options affect the binding.
And to actually do the work, typically the object that you are binding will listen for notifications, key value observing notifications, from the object that you're binding to. So whatever controller you bound your text field to, for instance. And making the synchronization possible is key value coding. Typically, whatever is implementing the bind method uses key value coding to get values from its controller. And also, when the user changes a value in the user interface, key value coding is used to set the value in the controller. So those are the obligations of the bound object, the object that you send the bind message to.
Now the reusable controllers that you would normally bind to, they're there to implement some of the logic that you would probably have to implement yourself, again, in any GUI-based app. They do things for you like managing the creation or deletion of objects or handling the notion of user selection.
And the reusable controllers that we ship, Go along the lines of the common classes that you would have, the common data structures you would have in your application. So if there's a single object that you're interested in handling, one object at a time, we have the NSObjectController. For arrays of objects and dealing with the notion of sorting or filtering arrays, handling selection in the array, we have the ArrayController.
If you have a graph of objects where each object has a relationship to a set of child objects, we offer the Tree Controller, which keeps track of the relationships between the objects. And if you have just some soup of key value pairs, we offer two controllers. First, the user defaults controller, which deals with the key value pairs you'll get from NS user defaults. And new in Leopard, we've added a dictionary controller, which actually will take a dictionary of key value pairs and convert it into an array of key value pairs, which makes it really useful for hooking up table views for editing user info, for instance.
Okay, so that was the brief overview. I hope I made that brief enough. Next, the commonly used bindings. These are all the things that just about everybody will end up using if they're using bindings at all. The format I'd like to take for this part of the talk is I'll describe some user interface and we'll go from there with a certain binding and figure out which controller and key path to use in order to get to that user interface.
We'll start off with something simple. User defaults. Building a preferences panel. In a simple example, we just have a few text fields and some check boxes, and we're really just interested in keeping whatever's in the text field in sync with whatever's in the user defaults. So since we're interested in values, we'll use the value binding.
And since we're interested in synchronizing with the user defaults, we'll use the NSUserDefaultsController. So we'd simply set up a binding from every text field we're interested in to the UserDefaultsController. It would look like this in Interface Builder. You would choose the SharedUserDefaultsController and use a controller key values, which is a special key in the controller.
And then the model key path that we would set is simply the value of whatever user default key we're interested in, the key of the user default key. So in code, it would look like this. We'd bind the text field's value binding to the user defaults controller with a key path, values.authorName in this example.
So pretty simple, pretty straightforward. And going along, most of the other interfaces that we'd be setting up with the value binding look pretty much the same. If we had an inspector instead of a preferences panel, we're still interested in binding the value of a checkbox, let's say. But instead of a user defaults controller, we would bind to an object controller, which holds the model object values.
But the binding looks pretty much the same. You're binding to a different controller, and you're using a different controller key, using selection instead of values. But you choose a key that you want to store in the object, and that's being held by the object controller. So in our example, you would write a binding like this to bind the checkbox value binding to an object controller using the keypath selection dot shadow enabled.
If you're interested in dealing with a table view and an array of objects, an array of model objects, the natural choice would be to use an array controller. We're still using the value binding, but now we can bind each column in our table view. We establish a value binding from each column to the array controller using a key path that represents the value in the column we want to display from each of the model objects. So in our case here, we bind our object column, we bind its value binding to our array controller using a key path arranged objects dot object name, some key.
And it's a similar process for outline views. Since outline views deal with hierarchies of objects, we would bind to a tree controller and we would bind its column again to a ranged objects dot sum key. So it's a simple process. They all look pretty much the same for the value binding.
If instead of worrying about the value held in a view, we're interested in making sure that its enabled state is kept in sync with some other state or some other object value, we can use the enabled binding. If we were to look at an example of enabling or disabling user interface elements based on some value in the user defaults, we could establish-- oh, OK. Imagine you don't want to be able to edit the attributes of the shadow unless you've said, turn on shadows, right? So we want this kind of user interface. Whoops.
Everybody remember this slide? Okay. So we can bind to something like the user defaults controller. It doesn't really matter. And the binding we would establish is the enabled binding. But the key path looks pretty much the same. Values.shadowEnabled. So we'd establish a binding from the text field or whatever control to the user defaults controller.
And remember that we had already bound, in an example, the value of the checkbox for whether or not the shadow is enabled. So we're actually binding the value binding and the enabled binding to the exact same controller and key path. So that way when the checkbox is toggled, the related binding for the enabled binding is also updated.
Straightforward? So it's pretty common to bind to some value in your model objects. But don't forget that there are other things that you can bind to. Anything that's key value observing compliant and key value coding compliant. So there's lots of state that's held in your application as well as the controllers.
So actually, I'm getting ahead of myself. I just want to show an example of binding to the controller selection. Again, we set up a value binding going back. We set up a value binding for the shadow checkbox and the enabled binding to the same key path and controller combination.
Something to consider, you can actually set up multiple enabled bindings on the same object. So imagine that there's more than just one state, more than just one reason for something to be enabled or disabled. Instead of having to create some accessor for it that aggregates all of the information that's required to figure out is it enabled or not, you can set up multiple enabled bindings to different controllers, different keypads. And the result of each individual enabled binding is added together so that you're only enabled if all of the enabled bindings say yes.
And this is one of a class of-- oh, OK, here we go. So this is what it looks like in Interface Builder, right? We've established one enabled binding. And I don't know if you can see at the bottom there, we have enabled two automatically pops up for us. If we set up a second enabled binding, So now I'll do enabled two. We see down at the bottom, enabled three pops up. And it goes on and on like this. There is a limit. I'm not telling you.
And the enabled binding is actually one of three bindings that we describe as availability bindings because they deal with whether or not a control is enabled, editable, or hidden. And the bindings are actually called enabled, editable, and hidden. Pretty straightforward. And they all support that enumeration feature, multiple bindings of the same name.
Okay, this is where I'd wanted to go before. So remember that you can bind to anything that is key value observing compliant. So not only does your application have a lot of interesting state that might drive whether or not something is enabled in the UI, but the controllers actually keep some state themselves based on what the user has selected, some other state, some other values in the controller.
So you could actually bind to whether or not there are any changes in, unsafe changes in the user defaults controller, or whether or not an array controller considers itself editable at all. Also, whether or not we should be able to add or remove objects to an array controller. And you can also do funny things like figuring out whether or not there are any objects available to display at all using a key path like arranged objects dot at count.
And you can play the same trick with the selection. So that, you know, you can actually bind to whether or not there are any changes in the user defaults controller. But you might not want to enable removing an object, any, might not want to enable a remove button if there's nothing selected to remove.
So we've gone over the value binding, we've gone over the enabled binding. This section on pop-up buttons isn't about any particular binding, it's about setting up bindings for the pop-up buttons. One of the things about the pop-up button that makes it so great is it's really configurable. You can set it up in many different ways and you can figure out what's selected in many different ways. What that means though is that there are lots of different ways to set up the bindings for pop-up buttons. I want to try and walk you you all through setting up these bindings.
We can look at all the bindings for the pop-up button in two classes. First, there are the set of bindings that deal with what's selected. On that side. And then there are the set of bindings that deal with what objects are actually put into the pop-up dynamically. I'll look first at all of the bindings that deal with the selection.
So for this, let's imagine a simple case. We've set up a pop-up button, NIB probably, by hand. So it has all of its menu items set up statically. There are four bindings for dealing with what's selected in that pop-up button. Selected value, selected tag, selected index, and selected object.
They're all mutually exclusive, so you can only bind one. So you have to know what you're interested in in the pop-up first. But keep in mind that you can set up a pop-up that has just a title, or you can set the tag of each menu item in the pop-up.
Or you might only care about the index of the item that's selected. And likewise, you can set a represented object for each menu item. So for each of those cases where you've set up your menu item by hand, you can use any of these selected bindings to figure out which menu item is selected.
Let's take a quick look at each of them. Again, imagine that you've set up the menu items for your pop-up, probably statically. And you're only interested in the selected value, so whatever label, whatever title is selected in the menu items. You can set up a binding like this. You bind the selected value of the pop-up to some controller using the key path that you want to be set in the controller. So if you're interested in the name of the last user, you'll get Miguel set in your user defaults controller.
And just an aside, this is what it would look like if you were to set it up in code. And for each of the other things that you can check in the selected menu item, there's a binding, right? There's the selected tag binding. If you've set tags on each of your menu items, it looks pretty much the same, but it takes a tag value, an integer.
And if you're only interested in the index of the selected item, then we have a binding for that, the selected index binding. And if you've set up the represented object and you're interested in the actual object that backs your menu items-- You can use the selected object binding. So that's what the four selection bindings are about.
Now, you might set up your menu items, your pop-up buttons menu items by hand in IB or even in code by yourself, but we can give you some help if you just have an array that you want to be shown in your pop-up button. And that's what these content bindings are for.
There are three bindings for setting up the content of a pop-up, and only two of them can be used on their own. Those are the content values binding and the content binding. The idea behind the content values binding is we'll take an array of strings and turn each string in the array into a menu item with that title. And we'll also set the represented object of each menu item to be that string. So we figure out, you probably want the string as a represented object. You don't have to do anything else.
The content binding takes an array of just ID objects. And what we'll do there is we'll assume each object in that array should be the represented object for the menu items we create. In order to figure out what titles should be in the menu items, we turn around and we ask each object you handed us for its description. So there are two ways to set up these bindings that automatically figure out what the title and the represented object for each menu item should be.
Oh, and there's no way to tell us what tag to put in each menu item. We just figure that the same value as the index would be fine. So we automatically set the tag of each menu item to be its index in the menu. So if we were to use the content values binding to set up some pop-up, we might bind to an array in our document that returns a string. And we'll directly create a pop-up with those menu items.
And again, we'll create tags for each of those menu items that corresponds to its index in the menu. Using the content binding, you return just an array of objects. And for each of those objects, you'll get its description, and that will become the menu item title. So that's the recap of content values and content. And keep in mind that all the selected bindings, the selection star bindings, are available still with those cases.
If you've used the content binding, like I showed you, we'll just figure the represented object should be the object that you handed us in the array, and the title should be the object's description. If you want to fine tune that and customize what's shown in each menu item or what the represented object for each menu item is, you can use the content binding in combination with either the content values or the content objects binding.
In all cases, though, we'll infer which-- we'll infer the tag for each menu item should be its index. So again, if you want to customize the way the title is created, you can use the content values binding in concert with the content binding. And the content objects binding will be using with the content binding to customize what the represented object should be.
And in both of these cases, they should have pretty much the same settings as the content binding. That means bind to the same controller and the same key path up to the last key component. So you can add one extra key to the content values or the content objects binding. I'll show you what I mean in a second.
So here we've bound, if you look at the bottom left, bottom right, we've bound to the same array controller that we bound our content binding to and almost the same key path. It starts off with arranged objects just like our content binding does, but in addition we add the model key name.
So this way, using the content and content values binding together, we get an array of objects, put those objects as the represented objects in our menu items, And then take the name of each of those represented objects and put that in as the title of each menu item.
Okay, lots of text, sorry. But you can also bind with the content and content objects bindings together. And it's the same effect, except now you're customizing what the represented object is instead of the title. And you can actually bind all three. Oh, if you're going to bind the content objects binding, keep in mind that that only really makes sense if you're interested in what the selected object is. Remembering that selected object binding keeps track of what the represented object of the menu item that's selected is.
And again, like I said, you can bind all three together. So you can start off with a base array that we get from the content binding. And then we'll customize the titles of each menu item we generate based on the content values binding. And the represented object for each of those menu items is customized by the content objects binding. You can use all three together.
But again, if you're using the content objects binding, you're probably most interested in the selected object binding later on, just to keep that in mind. Yeah, so lots of options. I swear it's easier to use than to explain. But OK. So common bindings, we're on to binding controllers to other controllers. Up till now we've talked mostly about binding views to controllers. You can actually bind one controller to another.
Typically you would do this to set up what we describe as a master detail interface. The idea behind a master detail interface is you're given some object and you're not really interested in that object. You're really interested in something that's related to that object. So if you were to have an array of people and each person has a picture, you're really interested in the pictures, but you have to navigate through every person in your array in order to find the pictures. So the UI would typically look something like this, right? Where we have an array of people on the left, and the selection in that table view of people drives what is shown in the detail view on the right.
In order to set this up, again, we're binding one controller to another. The idea is we'll take a master controller that deals with selection. It doesn't matter whether it's the object, array, or tree controller. And that's what drives the master side of our user interface. And then we take whatever controller is necessary to build our detail interface and we bind it to the master controller.
So again, we take the detail controller, in this case our array controller for pictures, and bind its content array binding to the master controller using the keypath selection dot, in our case, pictures. So we're basing the content of the detail controller on what's selected in the master controller. That's all it is, very simple concept.
Now though, imagine that we're dealing with multiple objects selected in the master controller. Something that might not be obvious initially is what happens when you have multiple objects selected in the master controller. Because now you have multiple choices for what to display in the detail. Should you display only the first set of pictures from the first selected person? Dealing with this might not be obvious how to deal with this.
You might want all of the pictures of all the selected people. But using just the content array binding doesn't give you that. But we do have, and also if you're dealing with multiple selected people, you probably aren't going to use the content binding or the object controller. So we'll focus on just the array and the tree controller.
[Transcript missing]
Okay, actually that's the summary of all the common bindings that people will use, well some of the common bindings. Now this is, I'll talk about some of the new things we've added to Cocoa Bindings for Leopard. The first thing is you can now do drag and drop with an outline view bound to a tree controller.
The magic that happened-- no, wait, there's no magic. All that we do is now we actually have a real object that can represent each node in the tree that the tree controller manages. That's the NSTree node. It's a really simple object. It has three core properties, right? There's a represented object that it handles, and that's the model object that you're handing it from your tree of objects. It also keeps a notion of a parent object and an array of child objects. So that way you don't have to manage a back pointer in your model objects.
And it also knows where it is in the tree of other tree nodes. So you can get to that using an index path, which is a class we also introduced in Tiger to represent exactly this, a path through a tree. So now that we have a real class to describe each node in the tree, we can use it.
In Leopard, and in the seed that you guys got, the tree controller will vend NSTree nodes through two methods. The selected nodes, which returns just an array of nodes that represent what objects are selected. And arranged objects on the tree controller returns a proxy that lets you navigate through the entire arranged tree as just going node by node.
And you can use tree node API on that proxy to start descending through all of the arranged nodes. And the API looks roughly like this. You can either use descendant node at index path if you already know where you're going to go. Or you can go one child node at a time and just get the array of immediate child nodes and traverse down from that.
Going back to the drag and drop with outline view thing, the real API that enables drag and drop makes it really easy is the move node API that we've added to Tree Controller. And this is a consumer for tree nodes. You pass it an array of tree nodes or a single tree node and we'll take that and put it in, we'll place it in the index path you describe. So using this, it's really easy to set up drag and drop in an outline view bound to a Tree Controller.
The process is pretty straightforward. You set up a tree controller the way you normally would given a children key path, a way to navigate through your model objects. And you bind the columns of your outline view to the tree controller. And you would set a custom class like either your files owner or subclass of NS tree controller as the data source for the outline view. You only have to implement the drag and drop data source methods. You don't have to implement any of the others you might be used to for the outline view data source.
And in these drag and drop methods, you're passed items from the outline view, right? These items are now NS tree nodes in the bound outline view case. And so now you can take a look at all the represented objects that item in the tree. And you can take a look at where the item is in the arranged tree by index path. So you can call represented object on the item. You can also call index path on the item.
And then once you're ready to actually do the move of nodes or drop a node into a new location, you can use TreeController move node to index path. You can also directly mutate the child nodes of each item. You can get access to that using the mutable child nodes method and treat it just like an array. This is a key value observing compliant array that you'll get.
And something about mutating these nodes, adding new nodes to the array, to the mutable child nodes, will fix up the relationships between the represented objects in each of those nodes as well. So this works great for the case of core data in your outline views, right, using core data managed objects.
And something to note, NSTreeNode is a useful class outside of NSTreeController as well. You can use it just to build ad hoc trees on your own just using TreeNode API. It doesn't depend on bindings or the TreeController at all. Okay, so that's new tree controller stuff. The other controller information we have is we've added a dictionary controller.
So why did we add a dictionary controller? Commonly, well, at least a lot of people claim it's common. Is it true? That you want to edit dictionaries in your user interface, something like you have a user info dictionary or anything like that, and you want to show the dictionary's keys and values in a table view.
It's typically difficult, it's challenging at least, to set up an array controller to transmogrify a dictionary into an array and show it in the table view and then actually handle the editing and everything all on your own. Well, the dictionary controller is there to handle that for you. It's a simplification of that process. It's a subclass of the array controller, which means it's really easy to set up with a table view.
And it does everything that you would expect, stuff that you would have to do yourself. Converting the dictionary into an array of key value pairs, making sure that certain keys are always visible or maybe never visible. And it even supports localization using a set dictionary, localization dictionary method, or using a strings file.
And typically, you don't want to show the raw keys in your dictionary to users, right? You could actually set up a pretty UI with pretty keys in your table view. And there's more information on this in the documentation. Setting up a dictionary controller with a table view looks just like setting up an array controller with a table view.
When you get the arranged objects from a dictionary controller though, they only have two keys, a key and a value. That's pretty much it. You get keys and values out of the dictionary as keys and values in your array. And like I mentioned, you can make sure that certain keys are always visible so that your user isn't presented with just an empty array the first time, an empty table view the first time they see your editor. So you can use the set included key method, this included keys method. But slides are boring. Let's do a demo.
Were they that boring? Sorry. OK. We have a simple application here. I used Core Data just because it was faster to set up the demo that way. We have just a person entity and a photo entity. Every person has an array or a collection of photos, right? Let's take a look at our nib. This is our nib. We'll build a simple editor UI just for looking at people and their photos. We'll start off with a window. So we have a few outlets in our .h. So we'll set up the outline view and the tree controller in our nib.
So we'll have names and favorite drinks. Do multiple selection. Drag over a tree controller. The first thing we'll set up before we forget is the children key path. This tells the tree controller how to traverse from one object to its related object. And in our case, every person has a photo. Photos. And if we look at our model, we've also set up a special attribute on each entity, the notion of isLeaf.
We could name it whatever we want, but it's useful for us to know that a person can contain other objects, but photos can't. is leaf is yes for photos and is leaf is no for person objects. So we'll tell the tree controller to take a look at that before it tries to go off and get any photos for photo objects. We're using Core Data, so we'll switch to entity mode in this inspector. And the Tree Controller will fetch person objects. And it should fetch it automatically, so I'll turn on the prepares content setting here.
Since we can't fetch objects in the TreeController without a managed object context, we'll go over here and bind the TreeController's managed object context. You might have noticed here our files owner is a persistent document subclass. That comes with its own managed object context. So we'll just bind to the files owner managed object context. Okay, you guys need to check to make sure that I spelled that right. That looks okay. Okay. Let's add some buttons. I like buttons. Since we're dealing with hierarchies, we can do more than just add, right? We can do add child.
We can wire these actions up to the tree controller directly. So we have add actions. Add child and if we look at the received actions, I also have remove. And now we'll set up the bindings for the columns. For the name column, we go to the value binding, the tree controllers, arranged objects dot name.
And for the drink, Now, in this outline view, since we're dealing with a tree of objects again, we'll start off with person objects, which all have a name and a drink, a favorite drink associated with them. But then we'll also show photo objects. And photos don't have drinks.
And we don't want our app to tell us that, you know, we already know that. So we'll turn off the raises for not applicable keys. That means that when we get a key value coding exception that photos don't have drinks, we'll ignore it and we'll keep running, right? We'll also turn off creating sort descriptors since we're not going to be able to sort on drinks if photos don't have a drink attribute.
Now we'll set ourself as the data source for the outline view. We also have the outlet to the outline view that we're interested in. And we also want to know about the tree controller. So with those things set, we can take a look at the .m. The first thing we do, window controller did load nib, we'll register for drag types. This is part of the setting up the outline view to be able to support drag and drop. Just come up with a key, nodes.
And all we're really interested in is knowing that we're about to start a drag. So in order to do the right items to the pasteboard, we just put an empty array, an empty string up there, but we hang on to the array that's passed to the right items method. This array is just an array of tree nodes that's been selected to start the drag.
The rest of the methods that you're used to for drag and drop, the validate drop method, will just make sure that, among other things, you can't drop into an object like, you can't drag photos inside of photos. Make sure that you're not trying to drag within the same parent. And always drag into a person, not just photo on top of photo. And it's always a move operation. So most of this is just general type checking, information checking that you would normally have to do with implementing drag and drop anyway.
And here is the real meat of the drag and drop implementation. That's actually performing the drop, accepting the drop. We've cached away the dragged nodes, as you saw earlier. We're just going to move those nodes to the new location. So item is the tree node that's going to be the destination for the drop. And the child index is the child index in that item's child nodes.
So we can create an index path, starting with that item's index path. and just adding its child index. And the move is done. So let's try it. Okay, add, add, add. Let's see, Bill likes gin. Kind of a rum guy myself. So we're adding some photos. We can just name them.
So you can see we've created the disclosure triangle for things that are person objects since those have child objects and photos don't. We can sort on name, drink. We've disabled sorting when we turned off create sort descriptors. And we can do drag and drop. One of the things about using the move nodes API, it's much better for the tree controller as far as being able to keep track of the selection. If you were to simply mutate the relationships underneath the tree controller, we'd try our best to keep track of the selection, but it's really much better to do this.
So that's drag and drop. Let's take a look though at the dictionary controller. Let's add a tab. So I don't have that much screen space. I want an editor for both the user info and for the picture data. Let's see. The user info will need a table view.
Kind of getting ahead of myself, though. We're going to need a dictionary controller to feed the table view. And since we want this to be a detail of whatever's selected in the person outline view, we'll bind the content dictionary of the dictionary controller to the tree controller's selection and the user info dictionary.
of that selection. And again, since not all objects have a user info, only person objects have user info, not photos, we'll turn off the raises for not applicable keys here too. We can add some buttons. Set those up to add in the dictionary controller and remove in the dictionary controller.
Let's see, remember about the enabled bindings? Let's take a look at using them here. We only want to be able to add or remove when a user is selected. So if the user is selected, The dictionary controller has any content via the user info binding, the content binding here. It'll actually set itself as editable. Otherwise, it will consider itself not editable. So if the dictionary controller is non-editable, then we shouldn't allow adding or removing. So we'll just bind the add and remove buttons.
But see, we don't want to enable them just because the controller is editable. We'd also want to make sure that you can remove things that the dictionary controller's notion of can remove is also satisfied. So here's that using multiple enabled bindings also. Okay, we'll go off and set up the dictionary controller to provide data in the form of a key. And a value to each of these columns.
Let's name them. And if I set this up correctly, So we add some person objects. We add a photo for each person. Sure enough. So we've selected a photo which doesn't have user info, so we've disabled this part of the UI. And now we can add, since we've selected Bill. So session, I think he's 248, 249. Then we can remove. Pretty cool.
Oh, but actually we always want the session information to show up. We're at WWDC. Somebody's got to give a session. Let's go back to the dictionary controller and under the attributes we can set the included keys. These are keys that will always be present. So session name. And then also, we don't want the UI for the table view to be enabled unless absolutely necessary. So we'll do the same trick here, binding to isEditable. And let's take a look at the photo.
We combine the data of the image well to the tree controller's selection and its image data, and it will directly create an image from the NSData that's in our managed objects. And I actually don't like switching between tabs like this. I'll use a pop-up button.
[Transcript missing]
Selected index. So now when we change the selection in this pop-up, it'll change the selection for us in the tab view. So then we can go with a tabless tab view. Now that I think of it, we should only be editable when we can actually drag in a photo. So when the tree controller has selected, has a photo selected.
Okay, so user info, photo, that didn't work. There we go. Let's try this. Let's open up an existing file. Yep, cool, sweet. And yeah, we still have drag and drop. We didn't break that. Cool. But actually, I don't like the pop-up either. The selected tab should really track what's selected in the outline view. We only have two things that could be selected. We have isLeaf as a property on each of the two things. They return different values. We could use a little trick here. We bind to the controller's selection.isLeaf.
The selected tab should really track what's selected in the outline view. We only have two things that could be selected. We have isLeaf as a property on each of the two things. They return different values. We could use a little trick here. We bind to the controller's selection.isLeaf. Thanks a lot.
So I have a few minutes in which to wrap up. The wrap up is practical information about, first, some of the reusable controllers that we ship. As you might have seen with the example, your interaction with the controllers should look something like this. You provide it with content, either using a binding or by specifically calling setContent on the binding. And then you set the object class name for the controller. So that affects which class of object is created whenever you hit the new or new button that's attached to a controller.
So once you've set the content and set up the controller properly, then the controller just manages everything for you. It deals with arranging the objects. For like the array controller, you could imagine it automatically deals with filtering or sorting the objects for you. It also deals with the notion of the user selection, keeping track of which object is selected, and dealing with what happens when the add button is pressed, creating new objects, or the delete button is pressed, deleting the objects.
The object life cycle of your model objects. I love managed objects. And when you're binding to these controllers, there's a certain pattern you'll get into with each of the controllers. When you're binding to a user defaults controller, you'll almost always be interested in using the values key as the controller key. Some key path like values.name.
With the object controller, it's a little different. You're normally going to bind to the selection in the object controller, which is always one object, but still pretty useful. With the array, tree, and dictionary controllers, since you can use these to populate UI like table views and outline views, you'll either be binding to the arranged objects dot something of these controllers, if you're populating the columns of a view, or if you're dealing with a single object, a single control, like a checkbox or a single text field outside of a table, then you're probably going to bind to a key path like selection dot something.
[Transcript missing]
So multiple objects in the selection, for example. But the accessors, selected objects and content, are just that. They're accessors. They're not proxies. They don't have any special behavior. They don't do anything special that's useful for some of these other bindings that we just showed you. They're really just arrays. And they're interesting from the standpoint of getting information out of the controller in just the array form.
And when you subclass these controllers, you're probably interested in a few things when you're subclassing. If you're subclassing any of the object controller subclasses, meaning array controller, tree controller, or dictionary controller, you're probably interested in handling the new object creation especially. So you can override the method new object that deals with how new objects are added or created when they're added to the controller.
Another reason for subclassing the array controller is you're interested in tweaking the way filtering or sorting works by default. So you can override the method arrange objects, which takes an array, and you return an array of arranged objects. And for the dictionary controller, you're probably going to want to override both. The new object method is where the new key value pairs are created that are added to the array. And arrange objects is also available if you want to do custom filtering and sorting again.
Subclassing the Tree Controller got more interesting with Leopard. We added some methods for navigating the model objects in a special way. Normally, in Tiger, you only had one option as far as key paths for navigating through your model objects. And you usually had to create extra accessors or categories in order to make this work. Now you can subclass the Tree Controller and have some say on a per-object basis, getting the represented object out of a node and figure out what the correct children or leaf or count key path for that object is.
And I've said it before, I want to make sure everybody's heard it. The actions that we hooked up to these buttons in Interface Builder, they're meant for being used in Interface Builder, right? If you send an array controller an add message, like the one you have here, the action method, then you're going to be surprised when you immediately ask for the count of objects in the controller. They won't have changed. These methods do their work delayed, so you'll have to wait.
There are methods, though, there are analogs for each of these that do their work immediately. So instead of add, you can use the add object method and add an object directly, similar to the remove object at index sort of methods that are available on all the controllers. Basically, anything that takes a sender on the controllers, you won't see the effects immediately. That's done so that we can, by default, produce some UI in Sheets. Sheets displaying any errors that happened while we tried to do the work.
So wrapping up the final bits, some practical info about using bindings. If you ever see an error like this in your log, Well, it doesn't really tell you anything other than something didn't have a key value coding key for something. If you turn on a default though, and it's binding debug log level to anything greater than one, there's really only one log level right now.
Then we'll log a little bit more information that describes like which key path, which object, and which object you were trying to bind, which object was involved, like the text field that was involved in the binding. Like what failed. So you'll get a little bit more information to try and track down where your bad key is.
You might also see some errors like this where an observer isn't key value coding or key value observing compliant for some key path. And the same thing applies here. If you turn on the debug logging, then we'll show you a little bit more information, which key path was involved, the object that you were binding to, and the object that was bound.
So this might help in tracking down any bad key paths or bad bindings. And remember that trying to observe directly like key or name on an array or a set isn't supported. Don't observe the collection. Try and observe the relationship. So the owning object is what you should be observing. So observe a person's photos, not the array of photos directly.
And another thing that you might see is that while trying to dealloc an object, there were still key value observing observers registered with it. Remember that trying to unbind or unregister observers in dealloc of your object is too late. You actually need to have some cleanup code like commonly in your Windows, window will close or your window controller methods. You can tear down all of the bindings using the unbind method.
A note about circular references. So in bindings, the bound object retains the object it's bound to. So that means that for our text field with a value binding that's bound to an object or user defaults controller, then the text field will retain the controller. And going down, if you bound the content of the controller to file's owner, hypothetically, the object controller will retain the object.
And that's what we call a bound object retains the object. So if you have a setter, you have an outlet to the object controller, and your setter retains the controller. I mean, what else would it do? You've suddenly got a retain cycle. So that means you'll be leaking parts of your UI if you close this window, right? Or you could get spurious messages, key value observing messages. It's important that you break this retain cycle using something like unbind or setting the content of the controller to nil before trying to get rid of the rest of your UI here.
Something to note, on applications linked on or after Tiger, if files owner is a window controller subclass, this should just work. And also, this should just work if you use garbage collection, right? Okay. And please, in order for bindings to do its work efficiently, it uses key value observing a lot.
So if you make changes in a non-key value observing compliant manner, we're probably not going to get the notification that anything changed. And so suddenly you'll see your UI has stopped updating. So make sure that all of the changes in your objects that you're interested in putting into your view, those changes have to be done in a key value observing compliant manner. There's lots of documentation on what that means. Please take a look.
So I mentioned that bindings, a binding is a contract between two objects where you're keeping the property of one object in sync with the property of another object. And that's the core concept behind Cocoa bindings, right? That's what we're trying to achieve. But you have, you as the owner of these UIs have a part of that contract to uphold yourselves.
That is, make changes in the right places. So it'd be breaking the contract to try and change the value of your model objects by going through the setter in your text field and hoping that the binding will push the value down to your model object. The text field takes user events.
The binding pushes the resulting values down to the model objects. If you want to make changes to values in your controller, make changes there in the controller. Or you can make changes directly on the model objects. Don't try and go in a circular route through your user interface elements.
There's some important information that you can check the documentation about the other bindings methods, the other key value binding methods. Bind is an important one. Equally important is unbind. There's also the info for binding method, which gives you some information about established bindings, which is great for being able to validate parts of your UI are actually hooked up.
You can use this programmatically. And please take a look at the NS Editor and NS Editor Registration informal protocols, which bindings use a lot to make sure that things like changes in your text fields are actually sent down to your controllers and your model objects when you close a window, for example.
So the summary, Cocoa Bindings is at its core two things. A collection of reusable controller classes and an API for establishing bindings, keeping the property of one object in sync with the property of another object. That's the core concept. And I want everybody to take a look at some of the new features in Leopard. The NS Tree node, NS Tree controller changes, the new dictionary controller. We've also added a new binding option to the pop-up button that I didn't get a chance to show you. All this stuff is well documented.
And for more information, see Derek. Actually, where is Derek? Well, find Derek. There's plenty of sample code for these things as well, good documentation. And if you want to ask more questions about it, there's a practice lab, Cocoa Bindings and Practice Lab on Thursday, Core Data Lab on Thursday as well. There's an AppKit lab. Wow, all the labs are on Thursday. Good Lord. So live in the lab on Thursday.