Application Technologies • 1:07:28
Gain an in-depth understanding about the new bindings capabilities in Cocoa for Mac OS X Tiger. You'll learn advanced binding techniques, selection handling, value transformers, master-detail configurations, how to establish bindings programmatically, and other advanced techniques.
Speakers: Ron Lue-Sang, Andreas Wendker
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Hello. Good afternoon, everyone. Welcome to session 136. This is going to be the Getting the Most Out of Cocoa Binding session. My name is Ron Luu-Saang. I work on Core Data and Cocoa Bindings. And Thursday is actually the best day for me during the conference. I'm just sleep deprived enough that I'm having fun, but I can still stand on both my feet. And everybody's been to the beginning, the primary binding session by Malcolm? Show of hands. Everybody's--ah, very good. Excellent. Everybody saw the Core Data session this morning? Oh, even better. Excellent.
Okay. The format for this session is we're going to skip all of the--this is model view controller. We're going to skip over all the beginner stuff. Assume that you've taken the plunge, started working with bindings at least, and we'll go over a grab bag of-- Yeah. --life topics that maybe we haven't had a good chance to tell everybody about before. So the roadmap will look like this.
We're going to go over some details about using the array controller, which is the controller that most everybody uses at least once in their app. We'll also talk about Tiger's new tree controller. And then Andreas is going to come on stage, and he'll bring up a list of advanced topics related to master-detail interfaces, binding up selections, like if anybody's had trouble binding pop-up buttons. We'll also talk about target and action bindings, target argument bindings rather. And then we'll finish up the session. I'll come back and talk about some really short, detailed topics, basically core data, debugging, and error presentation.
So I'll start off with, as I said, the controllers, the array controllers specifically. Everybody's probably used the array controller by now, has an idea of what it does. It controls an array of objects. And when you use the array controller, typically you'll use at least one of these three proxies, one of these three accessors, either arranged objects, the selection proxy, or the selected objects array.
So I'll talk about each of these three in turn, and then also some details about how IB actions work with the controllers, with the array controller, and how they're new to Tiger. So first, the arranged objects proxy. If you ask an array controller for its arranged objects, you'll get back an object that looks like an NSArray. And I guess it's bad for me to say it looks like an NSArray, because it really is an NSArray.
But it's a special NSArray that provides a view to the underlying content of your controller. This view can be sorted differently from the underlying content, and can often be filtered, so it'd be a subset of the content that your controller's managing. So you can imagine the look of it, sort of the content underneath, and your controller in the middle, providing you with this view, this arranged objects view of all of your content.
So when I say it's a proxy array, that means that as you work with the array controller, the contents of the array will change. So while you're changing the filtering on the array controller, or if you change the sort descriptors, if your user is pressing add and remove buttons, or if you change the content, the binding for the content of the controller changes, all these things will affect the arranged objects proxy array.
So that means that while you're using it, the count could actually change, for instance, or objected index might not be the same from one call to another. If you need this functionality where things won't change underneath you, simply make a copy of the arranged objects proxy. Those copies won't change underneath you.
[Transcript missing]
Okay, so that's arranged objects. The second proxy that I want to talk about is the selection proxy.
You ask an array controller for its selection, and it'll return a single object. And this single object gives you a way to do key value coding operations on all of the objects in the array controller's selection. So that means you can do setValueForKey or getValueForKey, just value for key, on the selection, and it'll be interpreted based on which objects are selected in the array controller.
This also implies that it'll handle multiple selection. And something about the selection proxy, it's KVO compliant, so that you can actually observe through the selection proxy. If you add yourself as an observer for selection.name, you'll get notified any time the controller's selection changes, or any time the name of the selected item in the controller changes.
So ordinarily, you'd see something like this in a simple UI, right? A table view fed by an array controller and a text field underneath fed by the selection of the array controller. In this case, it'd be bound to selection.lastname. But what happens when there isn't exactly one item selected in the array controller? If you were to ask selection value for key name, The selection proxy is smart enough to return a special marker. This is an NS controller marker. And ordinarily, the views in AppKit handle these markers in a special way. They can convert them into placeholders that you can specify on a per-binding basis.
So if we look specifically at each case, I don't know how well you guys can read it. In the case that there are multiple values selected in the controller, there's a special place for a multiple values marker. We also handle whether there's no selection at all in the controller.
And if the object that's selected doesn't respond to the key that you're bound to, for in this case, it doesn't return to middle name, it doesn't respond to middle name, for instance. And also, in the case of if there's no value at all to be had, you can set a special placeholder in the case that nil is returned from a value-for-key call on the selection.
Other cool things that work with KVC on the selection proxy are KVC operators. You might have seen these if you've looked at NSArray, which also supports KVC operators. This is basically asking value for key path and using a special key starting with an @ symbol. Here I've shown the @average operator.
What that'll do is go through all of the objects that are selected in the controller, ask for its salary, ask it for value for key salary, and then the controller's selection proxy, as well as any NSArray, will create an average of those salaries. Here are a couple of other constants that we support. If you find one that's defined on NSArray for these operators, the selection proxy will also support this.
So that's using the selection proxy. There are a couple of things that you can do if you want to affect how the selection is handled on the array controller. There are four flags. The first two handle basically adding and removing cases. So selects inserted objects and avoids empty selection. And the last two here, preserve selection and always uses the multiple values marker. Those are important if you're dealing with thousands, tens of thousands of objects and large selections. I'll go over each of these in turn, starting with selects inserted objects.
If you check out IB and bring up the attributes inspector for your array controller, you've probably seen this flag. Selects inserted objects does what it says. Every time an object is added or inserted, it tries to select that new object. The next one is avoids empty selection. In this case, when the selected object is removed from the array controller, if there's nothing left selected, if all of the selected objects are removed, basically, the controller will try to select the first object in the arranged objects.
The third option, Preserve Selection, tells the controller that if your content changes, if the controller's content changes, it should take a look at which objects are selected before the content change happens, and then find those objects in the new set of content that's handed to it. This is really handy for, actually really nice for a general UI thing, so that when somebody's working with a specific object in your array controller, if the content changes out from underneath them, but the same object is there in the new content, they won't lose their place.
When I say this has performance impact, it's because for very large collections of objects and for very large selections, this could be pretty expensive since we're scanning through the whole new content array trying to find the exact selected objects. So if you turn this off, you might find you'll get a performance boost for very, very large tens, hundreds of thousands of objects in your array controller.
And the fourth flag I want to talk about is the Always Uses Multiple Values marker. With this turned on, asking the selection proxy value for key path for some key, some key path, If there are multiple objects selected, that value for key call will always return the multiple values marker.
This may seem obvious, but normally when this is off, the selection proxy tries to figure out, well, what are the values for each selected object? So it'll go through every object that's selected in the array controller, getting its value for key, and if they happen to match, if everybody that's selected has the same first name, for example, then it'll return that common name.
And again, the performance impact of this should be clear. We're going to go through every object doing a value for key operation and comparing. So turning this on, turning this flag on, could save you a lot of time when you're dealing with large selections and lots and lots of content objects.
So that's influencing selection. The third accessor I want to talk about is the selected objects accessor. Asking an array controller for its selected objects returns just a plain array of objects corresponding to all of the objects that are selected. Pretty plain. Now this is important because this is a regular array with real content objects in them. It's not a proxy.
And so it differs in functionality from the selection proxy, since this is really useful for handing off to worker classes or worker methods or using in the target argument bindings or for master-detail interfaces. But since it is different from the selection proxy, it's important to note that you shouldn't bind or observe through the selected objects accessor.
It's much cheaper, it's much faster to bind or observe through the selection proxy. So to give you an idea, binding to just selected objects to find out when all of the selected objects changes, that's fine. But binding or observing selected objects dot name can be way more expensive than simply binding or observing selection dot name.
Okay, so those are the accessors. And the last thing I want to talk about specific to the array controller are IB actions. I've had a lot of people asking about this case where they've, in their code, gone through and changed the way, or I'm sorry, and added an object to the array controller programmatically, calling like array controller add colon. And I think that's something to note using these add, insert, and remove actions. In Tiger, all of these are performed delayed, so you won't see the effect directly in your code after calling them.
It's best to use, oh, actually, wrong slide. I want to mention can add, can insert, and can remove quickly. These are really nice for binding up UIs to make sure that you can only add, remove, or insert when the controller thinks it's correct. So if there's no selection, then you shouldn't have your remove button enabled.
Okay, and here you can see I have a UI where I've bound the remove buttons enabled binding to the controller's can remove controller key. This is what I had wanted to talk about in the first place. Yes, IB actions are performed delayed for all of the controllers. This is new in Tiger, and this is because we want to be able to present sheets when something goes wrong during these actions. So if you want to do an add or insert or remove in your code, it's better and much more performant to use these methods.
You can simply call add object, insert object at arranged object index, remove object at arranged object index. There are multiple, there's a singular and a plural version of each of these. And you should know that the controllers are optimized for these cases as far as when content underneath changes. So it's better for you to call these methods on the array controller than it is to take the underlying content array, mutate it, and then send a KVO notification. We'll do much less work if you simply tell us directly, I want to add or remove an object.
Okay, so to recap on the array controller, something that I skipped actually, even though the arranged objects proxy is liable to change as you work with the array controller, don't think that you should mutate it directly. It's not a mutable array. So never try to mutate the arranged objects proxy.
And similarly, you should note that mutating the underlying content, you really have to send a notification so that the array controller knows that you've changed something. Otherwise, you're making changes behind the array controller's back and it can't keep up with you. And something else to note, when you're putting together your model objects that you're going to put in your array controller, if you're going to bind to some property on these model objects, it really needs to be KVO compliant. Otherwise, things will... go wrong.
Further recap, remember, selected objects, binding through selected objects is a bad idea. Binding through selection is a better option. Same thing with content. When I say that you shouldn't bind or observe the content, or through the content accessor on the controller, that's different from when I say binding to, or binding the content of the array controller. That of course is totally supported, totally fine. Okay, so that's the array controller. Huh, thirsty. Next I want to talk about the object and the tree controller.
The slide I have for the object controller is pretty simple. But first, the controllers in general, you should feel comfortable using the object, the array, and the tree controller. They all kind of have the same design principles. You'll find selection on all of these controllers. The array and the tree controller have an arranged objects proxy, and these all work pretty much the same. And similarly for IB actions, all of the actions for all of these controllers are performed delayed. So with that, thinking about the object controller, something to note, the object controller is the superclass for the array controller and the tree controller.
But it just manages a single content object. It's most useful when you're putting together master-detail interfaces. You can use it as a proxy for another object in IB. So simple class. When we talk about the tree controller, it, of course, manages a tree. It manages a tree of objects. This is a little different from the way you work with the array controller and NSArrays, since we don't have an NS tree class in Cocoa. So instead, we ask you to simply configure the tree controller, telling it how to traverse your tree of objects.
So again, the Tree Controller uses an Arranged Objects Accessor. It has a selection accessor that works just the same as the Array Controllers. And its primary use, though, is to display objects in an Outline View or an NS Browser. It's really there just for Outline View and browsers to be bound to. And when you're using the NS3 controller with an outline view, it should feel just like binding a table view to an array controller. Similar thing with the way sort descriptors are applied.
So about that configuration thing, all you have to do is tell us the key path to follow for all child objects in your tree, in your object graph. Optionally, you can tell us about whether or not a special object is a leaf node, or you can return the count of the number of children of a particular object.
When you give us the key paths that we should use when traversing your tree, you just set them up in Interface Builder. The children key path is the one that's required. But if you give us more information, we can do more for you as far as how to display things in the outline view or the browser.
I want to talk a little bit more about each of these key paths. So you just set the key path in the tree controller as which key path to use, right? So we assume that all of the objects in your tree respond to this key path. And it is a key path, not just a key. And yes, it is really required. If you see an error saying so-and-so couldn't work with, work without a children keypath set, then you've forgotten to set this keypath.
And the easiest thing to do is usually just to write a category, or if you have the source, to write a simple accessor, a cover method, to return objects based on this key. If by design certain objects aren't supposed to have child objects, you can use the isLeafKeyPath and set that. What Isleaf does, the Isleaf keypath being set, prevents the tree controller from snooping for child objects.
If an object returns yes for is leaf key path, value for key, this leaf key path, then we won't ask for child objects. It is totally optional, but you might want to use it just so you can customize the way that the outline view or the browser shows disclosure triangles. You can see the logic here. It's pretty simple. If it's a leaf, then we'll never show a disclosure triangle, but if it's not, we'll always show a disclosure triangle.
While this takes precedence, you can also set the count key path for the tree controller. And this way, you can return the number of objects that are the children of a given object. This also affects the way disclosure triangles are displayed in the outline view or the NS Browser. Basically, it's make sure that the number of children is greater than zero. Then it will always show a disclosure triangle.
Side note that because you're telling us explicitly how many objects there are as a child, as children for an object, this Setting the count key path tells the tree controller not to ever enable the add, insert, or remove actions. This prevents what the tree controller thinks should be true from being out of sync with what's actually true in your model objects.
Okay. And IB actions for the NS Tree Controller, we've added add child as well as insert child. These go along with the add, remove, and insert actions that you're familiar with on the NSArray Controller. And we've also added the enabling keys for can-add-child and can-insert-child. So let's do a quick demo. Let's have IB running. Create a new nib.
I just want to show you how to set up a tree controller briefly. So here I set the children keypath. We'll add an outline view, resize our window. So again, it's supposed to feel a lot like binding a table view to an array controller. So you simply go to your outline views column bindings. Bind its value, let's say name, let's say here diameter. And we can also drag in a browser.
And the browser bindings are a little different from those for the outline view, since there's always just one value shown inside the browser. So we'll bind the content to the tree controller's arranged objects. The content values will be Tree Controller, Arranged Objects, and then the value we want to display. And then we'll bind the selection index paths of the browser to the selection index paths of the tree controller. And let's add some buttons so we can actually add stuff.
And we can connect these. You see here we have Add, just like the ArrayController has. And then there's Add Child. And yeah, I think that's it. Let's try that. So you see there's no disclosure triangle here because our logic simply says, well, there aren't any child objects. So not until we actually add one will we show a disclosure triangle. Let's see, and if you watch here, you can do lots of selection handling here. You can add lots of child objects. We all keep everything in sync between these two. Let's see, let's add one last thing. Talking about the selection proxy.
Using the display pattern value, I'll go through the selection diameter. So I can give you an idea of how this works. Oh, I have to turn on multiple selection first? Sorry. Okay. There we go. So complex math, all in IB. It's a fantastic thing. And that's it. Andreas will come up and talk a little bit more about advanced binding configs.
So good afternoon. My name is Andreas Wendker, and I manage the Xcode and Core Data frameworks. The vast majority of bindings that you use in an application are pretty straightforward. You bind some kind of property of a view to some property of a model object. So let's say the value of a text field to the name of a person.
But to get the most out of bindings for the more complicated cases, you often have to use combinations of multiple bindings and a variety of options for these bindings to really get, you know, to unleash the full power of bindings. So I want to focus on three topics: master-detail interfaces, selection control bindings, and and target argument bindings.
Let's start with master-detail bindings. So what is a master-detail interface? A master-detail interface is a situation where you have two types of model objects that are related to each other. And you partition your user interface in a way that you have one part in the UI that is responsible for showing information about one type of object.
You have a second part that is responsible for showing information about the other type of object. And you're making the content of that second part dependent on what is selected in the first part. So essentially, you have a UI that you see objects through a relationship of a selected root object.
So just to give you some examples that you might be familiar with. In Xcode, there's a groups and files view, and if you select a group, you see the files in that group in the list view. That's what we would call a master-detail interface. Other examples are the iTunes playlists view, where you select a playlist and you see the different kind of songs. In Mail, the mailboxes and the messages view. And in the core recipes application, which is the example we used throughout all the core data sessions at this conference, the groups and recipes. All these are good examples for what we call master-detail interfaces.
So how do you set them up with bindings? How do you solve this kind of problem with bindings? It's actually pretty straightforward. You use two kinds of controllers. One we call the master controller, the other one we call the detail controller. And then you just make the content of the detail controller dependent on the selection of the master controller. And you simply do that by binding the content of the detail controller to a key path like selection.yourrelationshipkey. That's pretty much all you have to do.
And then what happens is that the detail controller starts displaying what's in the relationship of the selected root object. And not only does it display it correctly, if you insert or remove objects in the detail controller, it will actually mutate the relationship. So with the two controllers and the content binding in between, you can fully display and edit relationships between different types of objects.
The type of content bindings you find on the controllers depend on the type of the controller. Not every controller has all the content bindings I list on this slide. But basically, depending on the type, you find three types of content bindings. And they're just there for three different types, or three different ways of organizing your model objects. So depending on what type of relationship you have, you use a different kind of content binding.
For ordered-to-many relationships, relationships that are represented as NSArrays, use the content array binding. For unordered relationships, unordered-to-many relationships, which by the way are the ones that Core Data is supporting right now, use the content set binding. And for to-one relationships, use the content object binding. It's all pretty straightforward.
One thing that you might wonder about is that if you know our controller classes, you know that we have controllers for single objects, for arrays, and for trees. We don't have a controller for a set. There's no NSSet controller. Instead, you have a content set binding on the array controller. Why is that? Well, the answer is that whenever you go into the UI, you really want to have some kind of a stable order for the objects.
It's really terrible for the user to see a UI where the order of the objects constantly changes. So the first thing you want to do if you have an unordered chip is you want to apply some sort order for it and then stick with it. And that's what the array controller gives us. If you bind to a set and you populate the controller with a set, then it will just put it in a fixed order and will try to keep that order as stable as possible.
Let's look at how this looks in IB. So what you see here on the screen is a typical Nib file with one window. And on the left side in that window, you see an outline view for groups. And on the right side of that window, you see a table view, let's say, for recipes. And then in the file window down there, you see that there are also two controllers, a tree controller for the groups and an array controller for the recipes.
What you see in the inspector there right now is the content binding of the recipes detail controller. And as you can see, it's bound to the groups controller with the key path selection.recipes. So our recipes controller always shows all the recipes selected in that group, all the recipes of the selected group. And then what the screen doesn't really show you is that, of course, you would have additional bindings for the right side of that window that shows the recipes that go to the recipes controller. And you would have additional bindings from the tree view to the groups controller.
So one interesting case that you will probably encounter is that the user might select multiple objects in the master controller. And if you followed what Ron said about the selection proxy, then you know that the master controller will then indicate multiple selections through the multiple selection marker object. So when the detail controller asks for its content, then all it gets is a marker object, and it really doesn't know what to do.
So by default, you just empty the detail controller. But we can do a lot better if you help it a little bit. There's a second binding that is an alternative binding for the multi-selection case that you can bind. So there's a chance for you to provide a separate key path, a separate value transformer, or the other types of options you find on bindings just for this multiple selection case.
And why this is so nice is, the reason why this is so nice is that for the arrays, like Ron also mentioned already before, we have operators that you can put in the key path to do something with the values. And there are a bunch of interesting operators for this case here, which are listed in the table. And I won't read them all to you in detail, but essentially they are operators to form a union of multiple arrays, of multiple sets.
So what you can do is you can just take all these selected recipes of all groups and form them all together in one large array that's then used for the detail controller. Let me give you a concrete example of that. So like before, we would bind the content set to selection.recipes, and the second binding, content array for multiple selection, we would bind to selection.atUnionOfSets.recipes. So now even in the multi-selection case, you still have a good array that you can use to populate the detail controller. Thank you.
There are two more options that I'd like to point you to for the content bindings. and one is called Handles Content as Compound Value. The other one is called Selects All in Setting Content. And those two options are a little bit on the complicated side, so I try to cover them with one slide each. For the first one, Handles Content as Compound Value. That option is typically used when you have a value transformer on your content binding.
Why would you do that? Well, sometimes you want to edit something as an array in the table view with an array controller, but you are not actually storing the data as an array on the master object. I've seen people using value transformers to take an NSDictionary and transform the dictionary, the key value pairs of the dictionary, into an array of individual line items that are then displayed and added in an array controller. Other things I've seen is that people are just archiving the entire array and storing it as an NSData on the master object.
So the display part of that works just fine without any help. You just use the value transformer, you apply it to the entire content, you get an array, you can display everything. Now for the editing part, the array controller will actually try to mutate the relationship in the intended way, which is to use the key value coding array mutator objects. So it will ask the master object mutable array value for key, which gives us an array proxy to mutate the relationship. And then we'll start inserting and removing and adding objects to that array.
Unfortunately, if you don't store the data as an array on the master object, that will, of course, fail. So with this option here, Handles Content as Compound Value, you can force the controller to bypass the normal relationship manipulation mechanism and instead just use simple set and get methods, just normal key-value coding, just set value for key and get value for key.
So then what happens is that the entire array, the entire content, if it's mutated, is taking its sense of the value transformer and just a simple set invocation on the master object. So the rule of thumb is pretty much whenever you use a value transformer for the content binding, you want to set this option.
The other one is selects all when setting content. The name is actually pretty obvious. All it does is that whenever the controller is populated with new objects, it will select them all. And this is useful in a variety of situations. And the most common one is probably when you write an inspector. So let's say you're in an application like Sketch. You have multiple documents open, and each document has a bunch of graphics objects that you want to edit in an inspector. And you want to, of course, allow editing multiple graphics objects at the same time.
So what you would probably do is you would go into Interface Builder, create a new Nib file, and start creating your inspector window. So you'd place text fields for the x and y coordinates. You would put text fields for width and height. And you place an array controller in that window, or in that Nib file, to which you bind the text fields. And then essentially what you do is that you just need to make sure that the array controller is always populated with all the selected objects in the current main window, in the current document.
And then together with this option here, the controller will make sure that all the objects are always selected, which means that all the text fields that you bound to the selection in the array controller will always edit all objects in there. So to give you the key path that a lot of people have asked me for for this situation is, so you're in a document-based application. You would bind to the NSApplication object, and you would use the key path main window, which gives you the current document, .delegate, which is the actual NSDocument object.
Let's assume that one has a controller outlet. That's the third component. And then you just bind to the selected objects of that controller. So the key path main window, .delegate, .controller, .selected objects will make sure that your array controller is always populated with the currently selected items in the main document.
Let's talk about selection control bindings. The typical situation is that you have some kind of pop-up button, and you want to manage the selected item in that pop-up button, usually to modify it to one relationship. So in some form, you want to control the selection in the pop-up, typically based on the represented object, maybe based on the tag or the index of the items in the pop-up, or maybe just by display value.
In addition to controlling the selection, of course, you need to make sure that the actual values, the available choices, are populated into the pop-up button. And you can do this in two ways. You can either do it manually in IB, just by going there and typing all the different values. More commonly, you can dynamically set or dynamically determine the content runtime.
So the bindings, the pop-up buttons have two types of bindings to solve these problems. There's a set of selected bindings that use to control the selected item. And there's a set of content bindings that use to populate the available choices. And of course, all that works, like with all the bindings for the various controls. It works with standalone controls, so a simple NS button, pop-up button. It also works with cells and table views. So if you put a pop-up button cell in the table view column, you can actually populate each pop-up in each row with a different kind of content.
So the four selected bindings that are available are pretty straightforward. Selected object controls the selection on top of the represented object. Selected tag, selected index, controlled by tag and by index. Selected value controls it by display value, by the title of the menu items in the pop-up button.
Now to populate the pop-up with the available choices, use a combination of multiple content bindings. There are three of them-- content, content values, and content objects. They all sound very similar. The ones you choose pretty much just depends on how your model objects are organized. And there are four cases that are interesting.
Case one is, you simply have an NSArray of strings, and you just want to put all these strings as display values into the pop-up button. So for that, you have the content values binding. Simply bind the content values to that array of strings, and then they will show up there. Now, since you don't give us anything else, just strings, there won't be any represented objects. But at least you can still use the selected tag, selected index, and selected value bindings. The selected object binding won't be very meaningful because there are no represented objects behind that.
The second case is that you have an array of represented objects. For that, you use the content binding. So you bind the content to the represented objects. There's a menu item for each object in that array. And since we don't know any better, we just use the description method of the objects to create a title to display. So now, what you have is you can actually use the selected object binding to edit your relationship directly because you have a meaningful represented object.
Now the most common case is the third case, which is the case where you have an array of represented objects, but each represented object also has a dedicated key that you can use to display a nice value for it. So you could have an array of person objects, and each person has a name.
So in this case, you'd bind two bindings at the same time. You'd bind first the content binding, and you'd bind the content values binding. The content binding points to the array of represented objects. The content values binding, on top of that, points to the display values. So this gives you actually the best of both worlds. You can use the selected object binding based on the represented objects from the content, and you can still have a nice display value in the UI.
So a concrete example, you have an array controller with person objects. You would bind the content to just the-- oh, so you have an array controller with person objects, and you want all the person objects to show up in the pop-up button. So you bind the content to the arranged objects of your person controller, and the content values to the arranged objects .name.
The only limitation here is that you need to be able to reach the display value through the represented object. You can't have two parallel arrays, one that stores all the represented objects, one that stores all the display names. You need to make sure that the display name can be reached through the represented object itself.
Case number four is a little different. In case number four, you store the represented object and the name in parallel within a container object. So typically you have an array of dictionaries, and each dictionary has two key value pairs. One for the name, for the display value, and one for the represented object. So in this case you bind all three bindings. Content points to the array of dictionaries. Content values points to the display property within there. and content objects that represented objects.
So again, you can use the selected object binding because we have a meaningful represented object. This time, though, the represented object is based on the content object's binding, not on the content binding. And you also have a nice display value through the content values binding. And the same limitation applies. The content values and content object bindings need to be based on the content bindings. You need to bind it to the same object. And the key path for the content binding needs to be the start of the key path for the other two bindings.
So here's a table that summarizes all this. I'm not going to read it again, but if you ever kind of get a hold of the slides, this might be good reference material for the future. There are a few more options I want to point you to for the content binding.
The most interesting one is the inserts-null-placeholder binding option. Inserts-null-value. God, I can't even read this. Inserts-null-placeholder. In combination with the null placeholder value that you can also enter in the inspector, what this does is that then the binding will create an explicit item in the pop-up to represent the null state. So you pretty much can create a menu item that the user can choose to explicitly deselect the value for the relationship.
Now we have the same kind of bindings that I just described for pop-up buttons, also for NSMatrix. So you can have an NSMatrix with radio or checkbox buttons. And then we will create the individual items in the matrix for you based on the content bindings. And you typically want to place the matrix in a scroll view because you don't know exactly how many items you will have. For the selected bindings, it will depend on the mode of that matrix what kind of bindings you have available.
In the default mode, the radio mode, where just one set can be selected at a time, you have the exact same bindings as the pop-up button has. But if you put the matrix in the highlight or list mode, which then allows multiple selections, you get two different bindings. You get selected objects and selected values, which represent arrays of all the currently selected items.
Let's talk about target argument bindings. The situation I believe a lot of us are in every now and then is that we're in IB, and we want to invoke some arbitrary method on some arbitrary object with some arbitrary number of arguments. Now, unfortunately, IB doesn't really give us a good, convenient way to do that, at least not in the traditional form without bindings.
So typically in the past, you had to go ahead and create some target action method, which means that you're limited to just one single argument with the sender of the argument. You would create a bunch of outlets to the different kind of UI elements that you want to read the arguments from. And then you go on your files owner, implement that method there.
And it's kind of a bit of ugly code to kind of put all this together just to invoke a method. So fortunately, with bindings, there's a simpler solution. You will use a combination of target and argument bindings on an NMS button. And there's pretty much three steps. First of all, you bind the target binding, and it will identify the object the method is invoked on. And as part of the target binding, you specify an option, which is the selector name of the method you want to invoke.
And then as the third step, for each argument of that method invocation, you use an argument binding. The argument binding is one of these funny bindings that are enumerated. So once you bind the first one, you get a second one, argument two. Once you bind that one, you get the next one, argument three, and so on.
In addition to NSButton, the target argument bindings, we also implemented the same functionality for double clicks on NSTableViews and OutlineViews. The bindings are named a little differently. They are called DoubleClickTarget and DoubleClickArgument, but they behave exactly the same way as the NSButton target argument bindings behave. So let's look at an example. Let's say you want to invoke a method open info panels for groups on your application delegate. And you want to take the argument, the groups, from the selection of a table view.
So there's two bindings. The target binding is bound to the shared application instance with a delegate key path. So the delegate of the application is the receiver of the method. And the argument binding is to the array controller that you'll probably use for that table view. And simply the key path selected objects. And then when the button is pressed, you invoke this method on the application's delegate with the currently selected items in the group's controller.
So an IB that would look like this, you would have a button with a table view, and then you'd have two bindings. As you can see, the target binding is bound to the shared application, keypath delegate, and the argument binding is bound to the group's controller for selected objects.
Another more complicated example, let's say you want to invoke a method, add keywords to pictures, show progress indicator, just because I had to make a Boolean argument. So you would want to invoke that on a files owner. The keywords are just an array of strings that you want to read from one table view, the currently selected strings in one table view. The picture objects are selected objects in another table view, and maybe that flag you want to read from the user defaults. So there are four bindings. Target binding goes to the files owner.
And interestingly, you can use self as a key path. So you don't have to actually go to another object. You can just use self as key path to invoke the method on the files owner itself. The first argument binding goes to the array controller for the keywords. You would probably use something like selected objects dot name as a key path. For the argument two binding, you would bind to the other array controller for the picture objects. Selected objects would be your key path.
And for the last argument binding, you would bind to the shared user defaults controller. And the key path would be something like, values dot my default name. So as you can see, you have a lot of flexibility with how you pull the different arguments together and to what kind of object you actually send it. Everything you can reach through a key path on an object that you can bind to is a potential target or a potential argument for the method.
So the selector name is in the-- you specify in the options area of the bindings, but it's really not an option. It's really a required option. Your application won't be very happy if you forget listing this. For the argument bindings, there are a bunch of interesting options. The first one I want to point out is the value transformer.
Most of you probably know that the value transformer is available for pretty much all bindings. It's particularly useful for the argument bindings, because often you don't have control over the APIs of the objects you want to invoke your method on. So the value transformers might just be the right choice to kind of transform the representation of the argument into the form that the receiver of the method expects it.
There's an option, allows null argument. So here you can decide whether you actually want to allow the method invocation if one of the arguments is still null. And in combination with the other option, conditionally sets enabled, which also is on by default, the binding will even automatically enable and disable the button based on the availability of the arguments. So you can pretty much say that as long as the user hasn't specified an argument yet, you don't want to allow the invocation, the button should be disabled.
And then the last option is invoke separately with array objects. This is a little more specialized. But if you don't have control over the API of the receiver, you might not have an API that takes multiple objects. But you still want to allow an invocation for all the objects in some selection of the table view.
So then you can pretty much split up the invocations into one invocation each per object. And that's what you can use this option here for. And as a last step, before I hand back to Ron, I'd just like to quickly give you a demo for how that could look like.
So here's an application that I have up and running. It's based on the Core Data Recipes example, but that doesn't really matter. It was just the most convenient way for me to fit in the data here. So as you can see, I have a master-detail relationship, recipes in the upper table, and then ingredients in the bottom one.
Depending on what I select in the upper table, the content changes in the detail table. And as you can see, it handles multiple selectors nicely by showing all ingredients of all selected recipes. I have a matrix here with the selected cuisines, and I can just go edit that.
As you can see, there are pop-up buttons. In the table view, we use the abbreviation of the measurement. And down here, we have the full name, like cup, tablespoon, ounces. So there's a bunch of pop-up buttons. And we even created a little method on the file zone that we want to invoke that will kind of tell us how much we have to go shopping for for all the selected recipes. So for 10 servings, I can click on this button to get the list of other things I need to buy into this pop-up button. So I wouldn't say this makes a lot of sense, but it's a good example. So let's take a look at the Nib file.
So there are a bunch of controllers in here for all the different kind of areas in your UI. There is the recipes controller, which is the master controller. You have an ingredients controller. And if you look at the bindings here, you can see that I bound the content set to the ingredients, selection of ingredients of the recipes. And I also bound the second alternative binding for the multi-selection case. And I'm using this union of sets operator.
The other thing that might be interesting is to show the pop-up button. There's content and content values. So I can show all the measurements. The measurement objects are the represented objects. The name of the measurement is what the display value is. And I also bound the selected object binding. So I'm using all three bindings together to, first of all, populate the pop-up button, and to secondly control the selection. And here for the content values, I'm using the name right on the table view.
I'm using the abbreviation, so you can even tune this a little bit. And then last, for the button here, as you can see, the target goes to the files owner with the key path save. So we invoke the method on the files owner with all the different kind of arguments.
And just to show you the code for that-- it's not particularly interesting-- but here's the method declaration. So I can pretty much invoke arbitrary methods on my files owner with all different kinds of arguments. And that's all I wanted to show you. And with that, back to Ron.
Thanks, Andreas. It's over there. Sorry. I love that demo. Okay. So everybody knows everything about content, objects, values, content, content objects, content values, content value objects. But I think you did a really good job explaining that. Okay, so going back to the rest of our talk, just go over a couple of small things, starting off with the changes we've made to controllers in order to work well with core data.
The main thing we've done is now the object, the array, and the tree controller have a few new features like having an entity mode, as well as the content set binding that we've seen throughout the show. And also, controllers can fetch content themselves, so you don't have to programmatically set a controller's content. They can fetch from a managed object context. A little detail about each of these things. First, the content set binding.
The content set binding really only makes sense for the array and the tree controller, so it's available there. And the reasoning behind it is, first, in core data, relationships aren't modeled as arrays. They're supposed to be unordered and not allow duplicates. So they're best modeled as NS sets. So in order to use an NS set with the controllers, we came up with the content set binding. What happens is we'll convert sets into whatever is appropriate for the controller you bound it with. So arrays for the array controller and trees of objects for the tree controller.
If you try to bind content set and you pass in an array, or you bind content array and you pass in a set, the controller will yell at you and tell you this. So you can check your debug or run log and look for this if something in your UI isn't working as you expected.
There are four steps to configuring a controller to fetch its content. Four simple steps. The two that are listed here at the top are required. The two at the bottom, of course, are optional. The first required step is setting an entity name for the controller. All you have to do is switch the controller into entity mode as opposed to class mode that you're probably used to and give it an entity name.
This entity is, the name of this entity tells it which objects to fetch from its data store, and it also defines which entity of objects will be created during insert or add actions. Step two, if you're going to make a controller fetch its own content, is give it some place to fetch from. So typically people will just bind the managed object context of your controller to files owner or some common place in your nib.
The first of the two-- actually, yeah, I should mention, when new objects are created by the controllers in entity mode, normally they're just created and inserted into a managed object context. But they're not assigned to any store that you would, by default, have control over. If you have multiple stores hooked up to the coordinator of your application, We'll just go through and find the first store that's writable, and your new object will be inserted into that store.
And the same, a similar thing happens during fetches. The controller just fetches from all available stores, if you have multiple stores hooked up to your coordinator. If you want to customize this functionality, you should consider subclassing the array or the tree controller or the object controller, and override new object in the case of when you want to assign objects to a specific store. You can override this and simply assign the object to a specific store. Or in your subclass, you could override fetch with request, as you see here, and set the affected stores for the fetch request that's passed in when you override this method.
Okay. Step three, one of the optional steps for having a controller fetch its content, is setting a fetch predicate. This is something that you'll only see when the controller is in entity mode. Normally, on the You're right. You'll see the keys that the controller has when it's in class mode. Oops. When it's in entity mode, you'll see a box down here for editing the fetch predicate that's used when the controller fetches its content.
So if you notice, we've talked about fetch predi-- about predicates twice in this talk, both in relation to the array controller's filter predicate, and also now for the more general case of a fetch predicate on an-- on an NS controller. That means there are two places to set a predicate, but they're not used in the same way.
The fetch predicate-- The fetch predicate affects which objects from your stores become part of the controller's content. So this is the underlying content array for the array controller. The filter predicate is set separately and affects which objects show up in the arranged objects array. So remember that proxy array that we talked about earlier. So this should emphasize that the arranged objects is always going to be a subset of the array controller's content array.
Okay, back to setting up the controller to fetch. You can optionally enable what we call live updating. This means that whenever the managed object context sends out its notifications for a new object has been inserted, a new object's been deleted, or an existing object's been deleted, or some object's been updated, some value's been updated, the context normally sends out a notification.
When the controller is set up to listen for these notifications, it'll automatically update its content, and you don't have to worry about it. Everything that happens in the managed object context will be reflected immediately in the controller. And even though we call it enabling live updates, the checkbox for it is called automatically prepares content.
Normally in class mode, automatically prepares content tells the controller, well, when we start up. In Awake from Nib, we'll create one of these, for in this case, mutable dictionary automatically if our content is nil. That guarantees that there's always something there. That's why we started off with automatically prepares content.
But now that we have entity mode controllers, we can reuse this flag. And during a wake from nib, if automatically prepares content is set to yes, then the controller will automatically fetch immediately right there in a wake from nib. And this also triggers the live updating functionality I talked about. This really provides a lot of functionality. It's a lot less work for you to keep objects in your context in sync with what's in your controller.
So that is the content set and the entity mode thing and fetching the content of the match. Okay, all right, so we're done with that part. Next I wanted to talk about a couple of small items. First, the enabled, hidden, and editable bindings we call availability bindings. And then we'll mention something about how errors are presented with bindings and then some tips on debugging.
So our availability bindings, we mentioned these earlier when I talked about the can add, can remove keys on the array controller or the tree controller. You can bind the enabled binding of some object, usually a text field or a button or an NS menu, to your controller or some object. And as soon as you bind enabled, a second binding becomes available, enabled two. These are what we call enumerated bindings. You saw these also in Andreas' talk related to the target and argument bindings.
What happens is, for the enabled and the editable cases, we'll AND all of the results for those bindings together. So only if all of the enabled bindings return "yes" for a control will we actually enable that control. And it's OR-ed for the hidden case, so that only if at least one of the hidden bindings returns "yes," then the object will be hidden.
Now on to error presentation. If you're familiar with the way commit editing works, this should be very interesting for you because now with commit editing with delegate, we can present sheets when something goes wrong committing edits from the UI. What happened in Panther is if you sent the message commit editing to a controller and some text field, for instance, was editing a bound value for that controller, telling that controller commit editing would have it tell its view that it should commit all of its edits as well. And if anything went wrong, we'd just return no.
This wasn't so great for presenting errors since we'd always present them modally. Now using the present error API that's new for Tiger, we can present errors in a different way. Ron Lue-Sang, Andreas Wendker Now on to error presentation. If you're familiar with the way commit editing works, this should be very interesting for you because now with commit editing with delegate, we can present sheets when something goes wrong committing edits from the UI. tiger, and this new method, commitEditingWithDelegate, bound controllers as well as NSManagedObjectContext can respond to this call by presenting sheets if something goes wrong, commit and edit in the related editor.
The last thing I want to talk about is debugging. A lot of people have brought up debugging as a source bot for bindings. And really, the most common thing that will happen is somebody's bound an incorrect key path. And usually you'll see something, an error like this in your run log. Another common error for the core data side of things is you're using a controller in entity mode, but you haven't given it a managed object context to work with. And then the controller will yell at you and say this.
But if you want to get some more debugging information, for Tiger we've added a default called the NSBindingDebugLogLevel. There's only one debug log level so far, so you can just set it to one. And watch your debug log or your run log, and you'll see some more detailed information whenever an undefined key exception is raised.
You'll see some information basically the key path that was bound to when the exception was raised, as well as the controller involved in the binding, and the object that was bound to the controller. So this will help you hunt down where a bad key path or a bad binding was set up a lot faster in your nib.
And for more information, check out the sample code. Oh, OK. Related sessions to this. Tomorrow morning, come in bright and early and give us your feedback. We're really excited to see so many people here. And we all want to hear your ideas, your feedback. So there's a Cocoa and Core Data feedback forum.
And tomorrow afternoon, there's advanced core data usages. It looked like everybody here was also interested in core data. And I think we might have already missed writing Cocoa Automator Actions. Oh, no, that's coming up. So you can also check that out, because Automator Actions in Cocoa makes use of bindings as well. And next. Who to contact? You can email Matt all of your ideas, all of your gripes, all of your feedback in general. And please, use Cocoa Dev. We take a look at that. We try and help people on the list there, too.