Skip to content

Multiple Windows with Core Data

2008 July 5

The majority of Core Data sample code is for either single-window applications or single-document-window applications. There are, however, times when an application or document requires more than one window on its data. When starting out learning to write code with Core Data, I spent a great deal of time trying to find a tutorial that covered multi-window applications; aside from Apple’s CoreRecipes application (which I found rather frightening to disassemble), there is very little reference information ‘out there’.

In this article I aim to show the way I code multi-window Cocoa data apps. I am by no means trying to say how all such code should be written and I definitely don’t claim that what I am currently doing is absolutely the best way to do things. Hopefully the article may help someone else who is beginning to write a multi-window Core Data application. Please accept my apologies if it is written at a level below your own knowledge; on the other hand if it assumes too much or you don’t understand something, feel free to contact me and ask.

– Edit – The example project for this post is available to download from here: coredatamultiwin.zip.

Before we start with the theory, let’s have a look at what we’re going to create. This sample application is a very simple example of a company ‘people records’ database application.

Screenshot of the finished application

The main screen displays a list of departments alongside a list of the people in a selected department; you can also click a button to view the details of a selected person in a new window. Clearly this particular application would be perfectly fine as a single-window application and I have deliberately made it as simple as possible. It doesn’t take much imagination, though, to see how this could be extended, making multi-window display essential. I have spent a large amount of time with commercial Fundraising database products like The Raiser’s Edge, and in these it is often necessary to view multiple records at the same time. You might, for example, need to look at individual screens for a husband and wife to see how much money they have donated as a couple, or perhaps have two different company records on screen to compare their history.

When working with multiple windows and Core Data, the key is to understand the primary concepts of the framework. When I started trying to write a Core Data application, it was great to see the walk-through tutorials that involve hardly any coding but this did lead to a slight frustration that I hadn’t got quite the right understanding of the underlying foundation to expand the given examples in the way I wanted. Understanding exactly what a Managed Object Context is and how it deals with the data held in a Persistent Store is very important to using multiple windows with Core Data. I will try to give more information about the core concepts as we move through the article.

I’ll assume that if you’re reading this, you know at least the basics of creating Cocoa and specifically Core Data apps so I won’t provide too many screenshots of that sort of task! Using Xcode to generate a new Core Data Document-based application produces a project with, amongst other things, a MyDocument object inheriting from NSPersistentDocument. NSPersistentDocument is similar to NSDocument in that it handles opening and saving files but adds built-in Core Data functionality for working with data in XML, Binary and SQLite formats. It is this document object (as with a traditional NSDocument object) that keeps track of the windows specific to an open document, keeping them separate from any general application-related windows that are handled by the application object or its delegate. To work with multiple windows in one document, we need to change the standard document object to work with multiple window controllers.

A window controller instance (of NSWindowController) acts as the control code for a window that is displayed on screen. Typically, you subclass NSWindowController for each different type of window that you display (in our example application, these would be the window that displays the lists of departments and people and the window that displays details of a single person). You then need to create an actual instance of an NSWindowController or its subclass for every single window that is displayed on screen.

First Steps

If you haven’t already, go ahead and create a new ‘Core Data Document-based application.’ By default, its NSPersistentDocument object called MyDocument is set to work with only one window that is taken from a nib file called MyDocument.nib. If you open the ‘MyDocument.m’ file, you will see that it uses the method windowNibName: to get the name of the nib file for the window and then opens the window contained within the nib, after which it calls windowControllerDidLoadNib:.

An NSDocument/NSPersistentDocument has two possible ways to open its document windows. When a new document object is created, if implementation code exists for the windowNibName: method, that method will be called and a single window will be created for use as the document’s window. If this code is not implemented, the document object will instead try to call its makeWindowControllers: method.

A document object maintains its own array of NSWindowController instances — ie an array of single NSWindowController or subclass instances for each window that is currently open for the document. When using the makeWindowControllers: method, opening a new window for a document requires that you allocate some memory to a window controller instance, initialize it as with any other object, and then add it to the document object’s array of window controllers. Each window can also be specified to be important enough to close the document object (and therefore any other open document windows) if it is itself closed. In our example application, the main document should be closed when the window that displays the list of people is closed but not when any individual windows of a person’s details are closed.

The Code

For now, we are going to change the MyDocument.m file to use makeWindowControllers: rather than windowNibName:.

Delete the windowNibName: method and replace it with this:

- (void)makeWindowControllers
{
	NSWindowController *mainWindowController = [[NSWindowController alloc] initWithWindowNibName:@"MyDocument" owner:self];
 
	[mainWindowController setShouldCloseDocument:YES];
	[self addWindowController:mainWindowController];
}

The code is fairly self-explanatory. It creates a generic NSWindowController instance using the MyDocument.nib file, specifies that it should close the document when the window is closed, and then adds the controller instance to the document object’s window controller array.

Build and run the application and as the default behaviour is to open a new, untitled document, our standard document window will open in exactly the same way as if you’d left the original windowNibName: code in MyDocument.m as it was when created. Choosing ‘File -> New’ will show a new window with a title to match that of the new and unsaved document (‘Untitled 2′, for example).

The Model

To create the data model for our sample application, open up MyDocument.xcdatamodel in Xcode.

  • Make a new entity called Department and give it a string attribute called name.
  • Make a new entity called Person and give it string attributes for firstName and lastName.
  • In the Department entity, create a to-many relationship employees with destination Person.
  • In the Person entity, create a to-one relationship to the Department entity and call it department.
  • Make the department relationship the inverse of the employees relationship.

My data model looks like this:
The sample data model

The Interface

We’re going to set up the interface manually rather than use the ‘option-drag from model to window’ approach so go ahead and open the MyDocument.nib file in Interface Builder.

  • Create a new NSArrayController, specify its Mode as ‘Entity’ and set the Entity Name to ‘Department’. It’s helpful to name array controllers to avoid confusion when using Cocoa Bindings so use the Identity Inspector tab to set the name to ‘Departments’.
  • Create a second NSArrayController for the ‘Person’ entity and name it ‘People’.
  • For both array controllers make sure the ‘Prepares Content’ flag is ticked. This flag tells the array controller to fetch data when it is instantiated from the nib file at runtime rather than waiting until you tell it to do so in code somewhere.
  • In the ‘Bindings’ Inspector tab, bind the ‘Managed Object Context’ to the ‘File’s Owner’ with Model Key Path ‘managedObjectContext’ for both array controllers.
  • For the ‘People’ array controller, bind the ‘Content Set’ to the ‘Departments’ ‘selection’ with Model Key Path ‘employees’.

Open the Window and delete the existing content label. Add two NSTableViews (one with one column and one with two) and some buttons to look like the interface below:
The layout of the main application window

  • Bind the Value of the single column of the left-hand table view to the ‘Departments’ array controller’s ‘arrangedObjects’ ‘name’ Model Key Path.
  • Bind the Values of the two columns of the other table view to the ‘People’ array controller’s ‘arrangedObjects’ ‘firstName’ and ‘lastName’ Model Key Path.
  • Control-drag from the ‘+’ and ‘-‘ buttons to the ‘Departments’ and ‘People’ array controllers’ ‘add:’ and ‘remove:’ received actions respectively.
  • Set the ‘Title’ of each table view column to match its content – ie ‘Department’, ‘First Name’ and ‘Last Name’.
  • Set the Selection mode of each table view to ‘Multiple’.

Go back to XCode and build and run the project. Everything should function as expected — you should be able to add and remove departments as expected, with employees being inserted into the currently selected department (and if no department is selected, the ‘+’ button under the employees table does nothing), and also Undo your actions.
The main application window in use

The interface might not be terribly pleasing but it is reasonable enough for this example application. If you wish, disable the ‘+’ and ‘-‘ buttons when these actions are disabled by their NSArrayController — easily accomplished by binding the ‘Enabled’ attribute on each button to the relevant array controller’s ‘canAdd’ and ‘canRemove’ keys.

Moving On

Now for a little theory. When you were making the array controllers in our sample application, you bound their Managed Object Context to the File’s Owner. It’s worth at this point saying a little about Managed Object Contexts before we continue with developing our application. If you are happy that you understand how Managed Object Contexts relate to Core Data, feel free to skip the rest of this section.

Managed Object Contexts

Apple documentation says you should think of a Managed Object Context as a ‘scratchpad’ for data. It also helps me to think of a Managed Object Context behaving like a cloud, hovering over the ‘persistent’ stored data. In a Core Data application, persistent data is stored in a ‘Persistent Store’. For this article it’s not necessary to go to much into Persistent Stores or their Co-ordinators but it is important to understand the keyword persistent. In Core Data, the data that is ‘persistent’ is the actual data which is physically stored on disc — for example when you choose to save a document in our sample application, the entries you have made in the Departments and People tableviews are made persistent and stored to disc. What then, are you seeing on screen before you save to a persistent store? Well, the data you are editing is being held in a ‘temporary’ place — in our Managed Object Context (MOC for short from now on…).

When you open a document in our sample application, the NSPersistentDocument object MyDocument automatically creates an empty NSManagedObjectContext object. As the main window is instantiated from the nib file, the Departments NSArrayController asks its MOC to load any persistent data for the Entity that you have specified, in this case the ‘Department’ records. Core Data is all about Managed Objects (hence the Managed Object Context) and these ‘records’, or instances of the ‘Department’ entity, are instances of NSManagedObject (or a custom subclass of NSManagedObject). If the current document is a new document, there are obviously no pre-existing persistent managed objects for the MOC to load.

Once the window appears on screen, you might add a department to the Departments array controller; the array controller tells its MOC to create a new managed object using the ‘Department’ entity specification, which you are then able to edit. It’s only when you Save the document that the MOC ‘rains down’ its data into the Persistent Store. You are therefore able to make multiple changes to managed objects, add new objects or remove existing ones, but nothing affects the data stored on disc until you actually tell the MOC to persist its data by telling the document to Save.

Let’s consider a case where you have previously saved a document with two Department records. When you open the document, the Departments array controller asks the MOC for any managed objects with the ‘Department’ entity specification. The MOC then asks the Persistent Store for these objects and retrieves them into its memory (in fact, Core Data uses a system of ‘faults’ to load data only when needed but I won’t go into this here). It remembers which objects in its memory relate to existing persistent objects such that when you tell the MOC to persist any changes, it updates the values in the persistent store. Imagine that you delete one of the two existing departments and add a new one — until you save, the data on screen at this point is that held only in the MOC. If you close the document without saving (or your application crashes…), the MOC will be deallocated from memory without updating the persistent store and your original document will remain unaffected.

The MOC is also what provides Undo support ‘for free’ in a Core Data application. When you choose ‘Edit -> Undo’, the MOC for that window is asked to step back through its recent activity. Again, nothing changes in the persistent store until you Save.

As a trick, go back and change the MyDocument makeWindowControllers: method to open a second window thus:

- (void)makeWindowControllers
{
	NSWindowController *mainWindowController = [[NSWindowController alloc] initWithWindowNibName:@"MyDocument" owner:self];
 
	[mainWindowController setShouldCloseDocument:TRUE];
	[self addWindowController:mainWindowController];
 
	NSWindowController *secondWindowController = [[NSWindowController alloc] initWithWindowNibName:@"MyDocument" owner:self];
	[self addWindowController:secondWindowController];
}

When you run the application, two windows will appear. Both will have the same MOC so if you add a Department in one window, it will also appear in the list in the other and vice-versa. And, at the risk of being punched in the face for repetition, the persistent store isn’t affected until you Save the document.

Make sure you change the makeWindowControllers: method back to its original ‘one window’ implementation before proceeding.

A New Window Controller Subclass

Let’s move on now to create the window that will display the information for individual employees in our application. In the existing nib file, the File’s Owner is set to be the MyDocument object (by the use of [...initWithWindowNibName: owner:self] in the makeWindowControllers: method) and it is this that provides the array controllers with their Managed Object Context (remember an NSPersistentDocument object automatically creates one managedObjectContext for your use). But, for our new person window, in order to keep track of which person that window represents we’re going to have to subclass NSWindowController; the File’s Owner of this new window nib will no longer be MyDocument but our new subclass. In order to be able to provide a MOC to the objects in the new nib file, we will have to keep a reference to it in our subclass, and be able to specify this when the window controller instance is created.

The Code

  • In Xcode, create a new ‘Objective-C NSWindowController subclass’ file called ‘PersonWindowController.m’.
  • In the PersonWindowController.h interface, add an NSManagedObjectContext pointer called _moc.
  • Add KVC methods to access this attribute using managedObjectContext: and setManagedObjectContext:, together with an initialization method.

Your code should look like this:

@interface PersonWindowController : NSWindowController {
 
	NSManagedObjectContext *_moc;
 
}
 
- (void)setManagedObjectContext:(NSManagedObjectContext *)value;
- (NSManagedObjectContext *)managedObjectContext;
 
- (PersonWindowController *)initWithManagedObjectContext:(NSManagedObjectContext *)inMoc;
 
@end

The initialization method will be used to set up a PersonWindowController instance with the specified MOC.

In the PersonWindowController.m file, add implementations for the methods thus:

@implementation PersonWindowController
 
- (PersonWindowController *)initWithManagedObjectContext:(NSManagedObjectContext *)inMoc
{
	self = [super initWithWindowNibName:@"PersonWindow"];
 
	[self setManagedObjectContext:inMoc];
 
	return self;
}
 
- (void)setManagedObjectContext:(NSManagedObjectContext *)value
{
	// keep only weak ref
	_moc = value;
}
 
- (NSManagedObjectContext *)managedObjectContext
{
	return _moc;
}
 
@end

Next we need to have code to open our new PersonWindows on demand:

  • In MyDocument.h, add declarations for an Interface Builder Action method:
    - (IBAction)openPersonWindow:(id)sender;
  • In MyDocument.m, be sure to import your new PersonWindowController.h header file.
  • Add a method implementation for openPersonWindow: thus:
    - (IBAction)openPersonWindow:(id)sender
    {
    	PersonWindowController *newWindowController = [[PersonWindowController alloc] initWithManagedObjectContext:[self managedObjectContext]];
     
    	[newWindowController setShouldCloseDocument:NO];
    	[self addWindowController:newWindowController];
    	[newWindowController showWindow:sender];
    }

The Interface

Now that we have a suitable window controller for our new window, we need to create a nib file for it to use:

  • Create a new Interface Builder Window nib file called ‘PersonWindow.nib’ and open it in Interface Builder.
  • Select the ‘File’s Owner’ icon and in the Identity Inspector set its Class to ‘PersonWindowController’.
  • Right click on the File’s Owner icon and set it’s Window outlet to the Window in the nib file. (This is a step I have often forgotten and then spent several hours trying to understand why my window didn’t appear!)

Next we need to make a button to open the new window:

  • In MyDocument.nib, open the main window.
  • Add an NSButton to the window called ‘Open Employee Window’.
  • Set the button action to the newly created openPersonWindow: method of File’s Owner.

Build and run the application. Each time you click on the ‘Open Employee Window’ button, a new, blank window will open. This window might not be particularly impressive as it doesn’t display any data, but at least it’s a start! Just to reassure yourself that the new window is a document window, make sure that closing the main document window will also close any associated employee windows.

– Edit – Handling enabling and disabling of the ‘Open Employee Window’ button is a little more complicated than the ‘+’ and ‘-‘ buttons. The hack way would be to bind its ‘Enabled’ attribute to the People array controller’s ‘canRemove’ key since canRemove is only true if a person is currently selected. The better way to do this, however, is described in a great post on this article at: http://www.passingcuriosity.com/.

Keeping track of the Person object

In order to display our employee data in the new window, we obviously need to be able to tell our personWindowController which employee it should display.

So, in the PersonWindowController.h file, add an NSManagedObject pointer called _person to the interface. Then add KVC methods to access it called person and setPerson: and change the initialization method so your code looks like this:

@interface PersonWindowController : NSWindowController {
 
	NSManagedObjectContext *_moc;
	NSManagedObject *_person;
}
 
- (void)setManagedObjectContext:(NSManagedObjectContext *)value;
- (NSManagedObjectContext *)managedObjectContext;
 
- (void)setPerson:(NSManagedObject *)value;
- (NSManagedObject *)person;
 
- (PersonWindowController *)initWithPerson:(NSManagedObject *)inPerson inManagedObjectContext:(NSManagedObjectContext *)inMoc;
 
@end

The initialization method has been modified to allow us to initialize with a specified person object. Change the method implementation in PersonWindowController.m and add the _person getter and setter:

- (PersonWindowController *)initWithPerson:(NSManagedObject *)inPerson inManagedObjectContext:(NSManagedObjectContext *)inMoc
{
	self = [super initWithWindowNibName:@"PersonWindow"];
 
	[self setManagedObjectContext:inMoc];
	[self setPerson:inPerson];
 
	return self;
}
 
- (void)setPerson:(NSManagedObject *)value
{
	// keep only weak ref
	_person = value;
}
 
- (NSManagedObject *)person
{
	return _person;
}

To know which Person object we need to pass to the window we’re opening, we need to be able to ask the ‘People’ array controller which object is currently selected. So, in MyDocument.h, add to the MyDocument interface an IBOutlet for the NSArrayController:

@interface MyDocument : NSPersistentDocument {
 
	IBOutlet NSArrayController *peopleArrayController;
 
}

Return to Interface Builder and connect this new outlet to the People array controller.

In MyDocument.m, change openPersonWindow: to the following:

- (IBAction)openPersonWindow:(id)sender
{
	NSArray *selectedPeople = [peopleArrayController selectedObjects];
	NSManagedObject *selectedPerson;
 
	int i;
	for( i = 0; i < [selectedPeople count]; i++ )
	{
		selectedPerson = [[peopleArrayController selectedObjects] objectAtIndex:i];
 
		PersonWindowController *newWindowController = [[PersonWindowController alloc] initWithPerson:selectedPerson inManagedObjectContext:[self managedObjectContext]];
 
		[newWindowController setShouldCloseDocument:NO];
		[self addWindowController:newWindowController];
		[newWindowController showWindow:sender];
	}
}

This code asks the peopleArrayController for the currently selected objects. It then moves through these opening a new PersonWindowController instance for each selected person.

In order to display the Person data from the NSManagedObject a particular window represents, we can use an NSObjectController that is tied to the window controller’s person key.

  • Open the PersonWindow.nib file and add an NSObjectController called ‘Person’.
  • Set its Mode to Entity with Entity Name ‘Person’.
  • Bind its Managed Object Context to the File’s Owner ‘managedObjectContext’, and its Controller Object to the File’s Owner ‘person’ model key path.
  • Add an NSArrayController called ‘Departments’, set its Mode to ‘Entity’ its Entity Name to ‘Department’, and specify that it Prepares Content.
  • Bind the Departments array controller Managed Object Context to the File’s Owner ‘managedObjectContext’.

Next add labels, text fields and a popup menu to the person window so it looks like this:
The layout of the employee window

To get the values to display in the window setup the following:

  • Bind the Value of the two text fields to the Person controller ‘selection’ ‘firstName’ and ‘selection’ ‘lastName’ respectively.
  • Bind the Content of the popup menu to the Departments controller ‘arrangedObjects’.
  • Bind the Content Values of the popup menu to the Departments ‘arrangedObjects’ ‘name’.
  • Bind the Selected Object of the popup menu to the Person ‘selection’ ‘department’.

After you’ve built and run the project, create a department and a person. Clicking the Open Employee Window button will now open a window displaying the current person with their details. When you change their first or last name (and either press Enter or tab out of the field to ‘End Editing’ on that field), notice that the entries in the main window change to match those in the person window. If you move a person into a different department, notice that they disappear from the list in the other window (assuming the old department is still selected, of course). Notice also that Undo automatically works for changes made in the employee window and if there are multiple people selected when you press the button, the right number of windows should open.

Final Touches

It would be nice if the person window that was displayed had a title relevant to the person it represents. When a window controller builds a window from a nib file, it calls the windowTitleForDocumentDisplayName: method which by default simply returns the supplied displayName attribute. To change this behaviour, override the method by using the following declaration in PersonWindowController.m:

- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName
{
	if( [self person] )
		return [NSString stringWithFormat:@"Employee: %@ %@", [[self person] valueForKey:@"firstName"], [[self person] valueForKey:@"lastName"]];
	else
		return displayName; // shouldn't happen but you never know...
}

Now whenever you open an employee window, it will be appropriately titled.

It would also be nice if the list of departments and employees were both sorted alphabetically. By default, Core Data displays objects in the order in which they were loaded which is arbitrary. To sort the two array controllers, add an IBOutlet NSArrayController called departmentsArrayController to match the peopleArrayController in MyDocument.h and connect it in Interface Builder to the Departments array controller. Also in Interface Builder, set both array controllers to ‘Auto Rearrange Content’. This ensures that the array controller will rearrange itself whenever any changes are made to its objects.

Next add the following code at the end of windowControllerDidLoadNib: in MyDocument.m:

NSSortDescriptor *departmentSortDesc = [[[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES] autorelease];
NSArray *departmentSortDescArray = [NSArray arrayWithObject:departmentSortDesc];
[departmentsArrayController setSortDescriptors:departmentSortDescArray];
 
NSSortDescriptor *personLastNameSortDesc = [[[NSSortDescriptor alloc] initWithKey:@"lastName" ascending:YES] autorelease];
NSSortDescriptor *personFirstNameSortDesc = [[[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES] autorelease];
NSArray *personSortDescArray = [NSArray arrayWithObjects:personLastNameSortDesc, personFirstNameSortDesc, nil];
[peopleArrayController setSortDescriptors:personSortDescArray];

Now the two array controllers will sort the displayed data in alphabetical order.

There is still the slight problem that we are able to open multiple windows for one person. To correct this, change the openPersonWindow: method declaration to the following:

- (IBAction)openPersonWindow:(id)sender
{
	NSArray *selectedPeople = [peopleArrayController selectedObjects];
	NSArray *openPeopleWindows = [self windowControllers];
 
	NSManagedObject *selectedPerson;
 
	if( [selectedPeople count] > 0 )
	{
		int i;
		for( i = 0; i < [selectedPeople count]; i++ )
		{
			selectedPerson = [[peopleArrayController selectedObjects] objectAtIndex:i];
			BOOL personIsAlreadyOpen = NO;
 
			// check to see if person window is already open
			int j;
			for( j = 0; j < [openPeopleWindows count]; j++ )
			{
				NSWindowController *eachWindowController = [openPeopleWindows objectAtIndex:j];
 
				// only ask PeopleWindowControllers if they are open for our person
				if( !personIsAlreadyOpen && [eachWindowController respondsToSelector:@selector(person)] )
				{
					if( [(PersonWindowController *)eachWindowController person] == selectedPerson )
					{
						[eachWindowController showWindow:sender];
						personIsAlreadyOpen = YES;
					}
				}
			}
 
			if( !personIsAlreadyOpen )
			{
				PersonWindowController *newWindowController = [[PersonWindowController alloc] initWithPerson:selectedPerson inManagedObjectContext:[self managedObjectContext]];
 
				[newWindowController setShouldCloseDocument:NO];
				[self addWindowController:newWindowController];
				[newWindowController showWindow:sender];
			}
		}
	}
}

There are various ways to accomplish this task; the above code checks through each of the document object’s window controllers to see if it responds to the person: method. If it does, it asks whether the selectedPerson is the person displayed by that window controller. If it is, showWindow: is called to bring it to the front. Otherwise, a new window controller is created and added to the document object’s window controllers.

Summary

At this point, we have a very simple but hopefully functional multiple window Core Data application. Making multi-window applications using the Core Data framework is actually not that difficult, it simply requires that you (understand and) keep hold of references to managed object contexts and pass around references to the NSManagedObjects held within them.

You might wish to keep a copy of the project in its current state as in the next article I’ll be expanding it by working with multiple managed object contexts.

If you’ve found this article useful, please let me know. If you think it could be made better (and I’m sure it can), please tell me and I will make the requisite changes!

Share
21 Responses leave one →
  1. October 4, 2008

    Thanx Tim,
    I really appreciate your examples. They’re very useful! Kind regards… Gianandrea

  2. March 21, 2009

    Thanks for all the examples. They were a big help in getting my head around how to do certain things.

    One thing about the “First Steps” section. I also read in the Cocoa documentation where it indicates that you have to delete the implementation of windowNibName: before it will call makeWindowControllers: … however, this seemed a little odd and when I went through your tutorial I left the code for windowNibName: in place, and simply called it in my override of makeWindowControllers: (instead of hard-wiring the NIB name into the makeWindowControllers: method).

    Works fine, at least with the current Cocoa. I hate to rely on behavior that’s contrary to documentation, but in this case maybe it’s a documentation bug.

  3. April 24, 2009

    Great tutorial once again, thanks! That was exactly what I was looking for. Is there any way to display not only the contents of the row that is moved during the drag, but also the background?

  4. Joanna Carter permalink
    July 10, 2009

    Tim, after many years of writing applications in both Delphi and C#, I had been struggling to comprehend how to create apps for OS X. Thanks to this article, I now have a simple working example to build upon. Many thanks.

  5. November 19, 2009

    Very useful article. I’ve been playing with exactly this issue for a couple days now and arrived at some but not all of the same conclusions you did. Your approach makes a lot of sense and I’m adopting it completely. Thank you for taking the time to put together this document. You saved me hours. -Allan

  6. January 14, 2010

    Gold……

    I had just ventured beyond using single nib, single window controller core data apps to something a bit more complex and was stumped. Your tutorial smashed it!

    Thanks for the work!

  7. Todd permalink
    July 15, 2010

    Thanks, Tim!!

    Really great explanation.

  8. September 9, 2010

    Really great stuff. I’ve been mentally struggling with how to mix multiple window documents with Core Data… Hillegass just wasn’t helping me bring these things together as needed… and this tutorial was just the trick and perfectly explained. I get it now… Thanks!

  9. Flavio Donadio permalink
    November 4, 2010

    Would these techniques work with a non-document-based CoreData application?

  10. admin permalink*
    November 4, 2010

    Absolutely; just pass around the managed object context from the application delegate instead of the document controller. You’ll obviously need to create and keep track of multiple window controllers yourself, though, rather than using NSDocumentController‘s default behavior and makeWindowControllers method.

  11. Chris permalink
    November 20, 2010

    One thing I don’t understand is where you say that _person and _moc are being set as weak references. Those setters don’t look any different than a normal setter, so what’s making them weak references?

  12. admin permalink*
    November 20, 2010

    A weak reference is one that isn’t retained. The setter for person, for example uses this:

    - (void)setPerson:(NSManagedObject *)value {
    	_person = value;
    }

    A strong reference would effectively be doing this:

    - (void)setPerson:(NSManagedObject *)value {
    	_person = [value retain];
    }

    though in reality, this kind of setter obviously needs to release the previous value (to avoid a memory leak), after making sure it isn’t the same as the new one (otherwise a release on what you think is the old object will actually release the new object before you can re-retain it).

    There are various different styles of setter method, e.g.:

    - (void)setPerson:(NSManagedObject *)value {
    	if( _person == value ) return;
     
            [_person release];
            _person = [value retain];
    }

    or

    - (void)setPerson:(NSManagedObject *)value {
    	[_person autorelease];
    	_person = [value retain];
    }

    If I were writing this post today, I’d use @property and @synthesize instead of including getter/setter methods:

    (in the header file)

    @property (nonatomic, assign) NSManagedObject *person;

    (in the implementation file)

    @synthesize person = _person;

    The assign indicates a weak reference on an object; replacing assign with retain would make it a strong one.

  13. Chris permalink
    November 27, 2010

    Ok, so “assign” can be used to create weak referenced setters. One more question, is the underscore character at the beginning of the variable names a standard practice when defining an instance that will only have weak references?

  14. admin permalink*
    November 28, 2010

    No, I use an underscore as a prefix for all my instance variables. This means I can tell instantly in a method whether I’m accessing an instance variable, or a local variable:

    - (void)someMethod {
        if( [_person age] == 21 ) { // do something }
    }

    In this example, it’s obvious I’m accessing the instance variable directly, when I should probably be doing this:

    - (void)someMethod {
        if( [[self person] age] == 21 ) { // do something }
    }

    If my instance variable was just named person, it wouldn’t be so obvious.

    Different developers use different ways to name their instance variables. Some use conventions such as m_person (‘m’ for member variable), others have the underscore on the end rather than the beginning.

    I’ve heard differing interpretations of Apple’s official advice on this convention. One thing that they definitely recommend against is using an underscore on the front of a method name (e.g., if it’s not defined in the class’s public interface so is a ‘private’ method that should only be called from within another method in the same class). Apart from anything else, this is the convention they use for their own internal “Private API.” If you’re submitting App Store apps with classes that use underscore-prefixed methods, you run the risk of being flagged for using forbidden Private API, even if you’re only using your own methods.

  15. Bill permalink
    January 16, 2011

    This post was great in helping me understand NSWindowControllers MUCH better. Thanks!
    I did need to bind the popups selected value to ‘Person selection department.name’ otherwise the popup text was set to Not Selected.

    This was with XCode 3.1.2 on 10.5.

Trackbacks and Pingbacks

  1. Blog @ Tim Isted » Blog Archive » Sample project code now available for all posts
  2. Passing Curiosity » Blog Archive » Resources for Learning Cocoa
  3. Passing Curiosity » Blog Archive » Binding Operators With Core Data
  4. User links about "coredata" on iLinkShare
  5. Core Data « Grahammacdonald's Blog
  6. CoreData 简介 | Geeklu.com

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS