Core Data and Cocoa Bindings

Changes made to objects’ property values should be propagated to the user interface, and user interface elements displaying the same property should be kept synchronized. Cocoa bindings provides a control layer for Cocoa but, whereas the Core Data framework focuses on the model, Cocoa bindings focus on the user interface. In many situations, Cocoa bindings makes it easy to keep the user interface properly synchronized. The Core Data framework is designed to interoperate seamlessly with, and enhance the utility of, Cocoa bindings.

Cocoa bindings and Core Data are largely orthogonal. In general, Cocoa bindings work in exactly the same way with managed objects as with other Cocoa model objects. You can also use the same predicate objects and sort descriptors as you use to fetch objects from the persistent store to filter and sort objects in memory—for example to present in a table view. This gives you a consistent API set to use throughout your application. There, however, are a few (typically self-evident) differences in configuration and operation.

In addition to the issues described in this article, there are a few other areas where the interaction between Core Data and Cocoa Bindings may cause problems; these are described in “Troubleshooting Core Data,” in particular:

Modulo these exceptions, everything that is discussed and described in Cocoa Bindings Programming Topics applies equally to Core Data-based applications and you should use the same techniques for configuring and debugging bindings when using Core Data as you would if you were not using Core Data.

Additions to Controllers

The main area where Core Data adds to Cocoa bindings is in the configuration of the controller objects such as NSObjectController and NSArrayController. Core Data adds the following features to those classes:

Automatically Prepares Content Flag

If the "automatically prepares content" flag (see, for example, setAutomaticallyPreparesContent:) is set for a controller, the controller's initial content is fetched from its managed object context using the controller's current fetch predicate. It is important to note that the controller's fetch is executed as a delayed operation performed after its managed object context is set (by nib loading)—this therefore happens after awakeFromNib and windowControllerDidLoadNib:. This can create a problem if you want to perform an operation with the contents of an object controller in either of these methods, since the controller's content is nil. You can work around this by executing the fetch "manually" with fetchWithRequest:merge:error:. You pass nil as the fetch request argument to use the default request, as illustrated in the following code fragment.

- (void)windowControllerDidLoadNib:(NSWindowController *) windowController
{
    [super windowControllerDidLoadNib:windowController];
 
    NSError *error = nil;
    BOOL ok = [arrayController fetchWithRequest:nil merge:NO error:&error];
    // ...

Entity Inheritance

If you specify a super entity as the entity for a fetch request, the fetch returns matching instances of the entity and sub-entities (see “Fetching and Entity Inheritance”). As a corollary, if you specify a super entity as the entity for a controller, it fetches matching instances of the entity and any sub-entities. If you specify an abstract super-entity, the controller fetches matching instances of concrete sub-entities.

Filter Predicate for a To-many Relationship

Sometimes you may want to set up a filter predicate for a search field that lets a user filter the contents of an array controller based on the destination of a to-many relationship. If you want to search a to-many relationship, you need to use an ANY or ALL in the predicate. For instance, if you want to fetch Departments in which at least one of the employees has the first name "Matthew", you use an ANY operator as shown in the following example:

NSPredicate *predicate = [NSPredicate predicateWithFormat:
    @"ANY employees.firstName like 'Matthew'"];

You use the same syntax in a search field's predicate binding:

ANY employees.firstName like $value

Things are more complex, however, if you want to match prefix and/or suffix—for instance, if you want to look for Departments in which at least one of the employees has the first name “Matt”, “Matthew”, “Mattie”, or any other name beginning with “Matt”. Fundamentally you simply need to add wildcard matching:

NSPredicate *predicate = [NSPredicate predicateWithFormat:
    @"ANY employees.firstName like 'Matt*'"];

You cannot, though, use the same syntax within a search field's predicate binding:

// does not work
ANY employees.firstName like '$value*'

The reasons for this are described in Predicate Programming Guide—putting quotes in the predicate format prevents the variable substitution from happening. Instead, you must use substitute any wildcards first as illustrated in this example:

NSString *value = @"Matt";
NSString *wildcardedString = [NSString stringWithFormat:@"%@*", value];
[[NSPredicate predicateWithFormat:@"ANY employees.firstName like %@", wildcardedString];

By implication, therefore, you must write some code to support this behavior.