You are on page 1of 44

The Developers Magazine

published for members every two months by The Developers Group


incorporating the DotNET Developers Group and Delphi Developers Group January/February 2010
This issue celebrates The Best of Delphi 2005 and Dr Bobs birthday

Contents

This special issue is dedicated to Joanna Carters Exploring Mac Development tutorial.
In it, Joanna talks us through the creation of a small application, destined to run on a Mac computer, using the Apple developer toolkit of Xcode and Interface Builder. The tutorial has been devised in association with Joannas Exploring Mac Development session at our meeting in Reading on February 15th.
DG 2010 Meetings and Masterclasses
January February March April Wednesday 13th Monday 15th Wednesday 17th Monday 12th & Tuesday 13th Monday 19th Wednesday 19th Monday 7th Victoria Reading Victoria Bob Swart .Delphi Reading Victoria Reading

May June

Dates for second half of year to be confirmed July August September October November December Wednesday 14th no meeting Monday 20th Wednesday 20th Monday 15th no meeting Victoria Reading Victoria Reading

At the meeting, Joanna will be discussing native Mac development while Brian Long approaches Mac development from a Delphi and Windows perspective.
The Developers Group, run by Joanna Goulson and friends, is a division of Richplum Ltd, 7 Devizes Road, Upavon, Wiltshire SN9 6ED, U.K. telephone 01980 630032 fax 0800 066 4336 e-mail joanna@richplum.co.uk websites www.dotnetuk.com and www.richplum.co.uk

50 Vo uc he rs
.NET Reflector enables you to easily view, navigate, and search through, the class hierarchies of .NET assemblies, even if you dont have the code for them. With it, you can decompile and analyze .NET assemblies in C#, Visual Basic, and IL.
Red Gate Software, who sponsor refreshments at DG meetings, are looking for testers. (This is open to all our members, whether or not you are in the U.K.) Red Gate say:
Our usability testing focuses on the tools currently in development. At this moment we are looking for professional .NET developers who havent seen or used the upcoming .NET Reflector Pro to participate in our studies. It is vital that a potential participant has no prior experience with the new version. If you have members who have an application that they would like to step into using Reflector Pro functionality in Visual Studio then wed be very keen to talk to them. Should people be interested but dont quite match the requirements wed be more than happy to add them to our list of contacts for future tests. We would be especially interested in developers who are currently working on ASP.NET applications written in C#. The sessions are set up via our secure NTR remote connection and usually last about 60 minutes. Afterwards we send the participants 50 Amazon vouchers in return for their time. All the best Adam Walker usability@red-gate.com 0044 (0) 1223 438 8520 www.red-gate.com Ingeniously simple tools http://www.red-gate.com/about/user_experience/index.htm

Exploring Mac Development


by

Joanna Carter

Contents
The Object Model! Creating the Application! Designing the Object Model! Creating the Classes from the Model! Designing the Visual Interface!
Connecting a Button to a Controller!

1 1 3 5 7
9

Creating a Simple Browser Form!


Creating a Window Controller! Connecting the Languages XIB le to its Controller Class! Adding an NSArrayController! Designing the Languages Window! The Table View! Conguring the Add and Remove Buttons! Showing the Languages Window!

11
12 14 14 15 16 18 19

Preparing the Main Form!


Preparing the Controller! Preparing the XIB File! Setting up the Main Form! Connecting the Columns to the Array Controller! Conguring the Add and Remove Buttons!

19
19 20 20 21 21

Creating a Modal Dialog!


Creating the Controller! Creating the XIB le! Setting up the Word Controller! Setting up the Languages Controller! Setting up the Dialog! Connecting the Buttons! Binding the Controls! Activating the New Word Dialog from the Main Form!

22
22 27 28 29 29 29 30 31

Creating an Editing Form!


Creating the Controller! Creating the XIB le! Showing the Edit Word Window!

32
32 34 35

Bonus Draw(er)!
Activating the Drawer! Designing the Drawer Content!

37
38 39

Conclusion!

39

Exploring Mac Development


This tutorial is intended to be read in conjunction with a presentation of the subject at the UK Developers Group. If details appear to be missing or wrong, you should contact the author for further explanation. The purpose of this tutorial is to describe the creation of a small application, destined to run on a Mac computer, using the Apple developer toolkit of Xcode and Interface Builder. The application will perform the simple task of storing and retrieving words, with a link to the language in which they are dened; e.g. House/English, Maison/Franais. Although this may seem like a trivial task, it will serve as a low-level introduction to several aspects of Mac programming, making use of some key concepts and technologies.

The Object Model

As can be seen from this class diagram, the Language class has but one attribute or property - that of the name of the language, whilst the Word class has an attribute that holds the text of the word and a relationship or association to an instance of the Language class.

Creating the Application


We shall be using the Core Data framework to dene our business classes; this gives us the advantage of providing automatic object persistence, in a choice of binary, XML or SQL storage mechanisms. We will use the default XML mechanism for our application, as this allows us to inspect the data, in a human-readable form, without the need for anything like a database management tool. Once the application is completed and we are satised with the testing, we can easily change the persistence mechanism, simply by changing a constant in one line of code. So, let's start by opening Xcode and creating a new project:

page 1

This tutorial was written using the latest available version of Xcode (3.2.1) so you may not see exactly the same screens and dialogs if you are using earlier versions. The New Project dialog shown above is a case in point - the dialog on the left is from Xcode 3.2.1, the one on the right from Xcode 3.1.3. The earlier dialog, on the right, is not as clear in telling us that the selected Core Data project is also a Cocoa application but is, in fact the correct choice for this tutorial. We are going to create a Cocoa Application, not forgetting to check the "Use Core Data for storage" option then, after clicking on the Choose button, we will be asked to give the project a name - we will use "Words".

When the IDE appears, we will be able to open out the various sections of the project, to see that Xcode has already created several les for us, including an empty object model, an application delegate class, the main entry point class, and a XIB le, which contains our main form. The Objective-C compiler has undergone several changes over the last couple of years; one additional feature is the ability to use Garbage Collection instead of the traditional reference-counted memory model. If you are planning on developing applications for the iPhone, then you, presently, have no choice but to use the reference-counted model but, in order not to make this tutorial any more complicated than it has to be, we shall avoid learning about terms like "retain" and "release" by using Garbage Collection. So, before going any further, we need to change the memory management for this project, from the default reference-counted model, to the garbage-collected model, which will make life easier by avoiding possible memory leaks or premature release of objects. To do this, select the topmost node in the project tree and click on the Info button on the toolbar; this will show the inspector. Choose the Build tab and use the spotlight eld to search for "garbage" to see the setting that needs changing from "Unsupported" to "Required".

page 2

File Locations
I prefer to have all my class les in a sub-folder of the main project; so one of the things I do, when the project is rst created, is to switch to Finder and create a "Classes" folder, under the main project folder, and move the default Xxx_AppDelegate class les there. The "folders" in the Xcode project are actually just logical groups and, after moving the les, Xcode will mark the delegate class les (in red) as missing. To x this, you will need to select the "Classes" group in the Xcode project, click on the Info button on the toolbar and use the "Choose" button to navigate to the newly created "Classes" folder.

Designing the Object Model


Whereas, with development environments such as Visual Studio and Delphi, we would be encouraged to start designing forms, maybe not even bothering to design business classes, the Apple development tools promote the use of the MVC (Model View Controller) design pattern, as well as encouraging the use of an object persistence framework, known as Core Data. This means that, when we start a project, we can start by designing our business classes and use those class denitions to help us design the forms on which to present and edit instances of those classes. It also means that we don't have to bother about database design and maintenance, as this is all managed by the Core Data framework. The Xcode IDE contains editors that allow us to interact with code units, resource les for localisation, etc. and the object models that make up our problem domain. The only thing that Xcode doesn't do is directly edit user interface les such as forms and dialogs - these are managed through the separate Interface Builder application. However, simply double-clicking on a XIB (user interface) le will either open or switch focus to Interface Builder, allowing changes to be made to the XIB le. Interface Builder will update automatically to reect any changes made in Xcode that might affect the XIB le; likewise, XCode can respond to changes made in the XIB le.

page 3

So, the rst thing we need to do to create a Core Data application is to dene our business classes, using the empty .xcdatamodel le that was added as part of the project for us when we created it. Selecting the .xcdatamodel le will reveal the modelling surface, upon which, we will create our object model. If you cannot see the modelling surface, you may need to drag a splitter bar, which is hiding at the bottom of the window, so that the layout resembles the above screenshot. We create Entities by either right-clicking on the design surface and choosing to create a new entity, or we can click on the "+" button at the bottom of the Entity section of the browser above the design surface; either way, we need to create two entities, one for each of our two classes: Word and Language.

The Names need setting to "Word" and "Language" respectively; the Classes will be automatically updated when we come to creating class les in the next stage of the tutorial. The next step is to add properties to the entities by either right-clicking on an entity in the diagram, or clicking on the "+" button at the bottom of the Property section of the browser above the design surface. Starting with the Language entity, whichever way we choose to add an attribute, the result should be that the Attribute section of the browser looks like the screenshot below left, after we have entered the name of the attribute, selected its Type and chosen whether we want the value to be optional and/or indexed. This diagram shows the "name" property of the Language class as being a string, indexed and whose value is required. The convention, in Objective-C, is to use camel-case capitalisation for property names, starting the name with a lowercase character.

Adding a relationship or association property to the Word class, whose destination is the Language class, gives us the above screenshot where "language" is specied as the property name and whose value is also required. We should also add a required, indexed, string property called "text" to the Word class, to give us the complete class diagram, as shown at the start of this section of the tutorial.

Because we have not dened an inverse relationship, from the Language class, back to the Word class that contains the language property, we will get a compiler warning (which will appear in the Build Results section of the IDE) whenever the compiler has to recompile the object model. Normally, this inverse relationship is optionally used by the object persistence mechanism to look after matters such as cascading delete actions, etc. For the purposes of this example, this is nothing to worry about and the warning can be safely ignored.

page 4

Creating the Classes from the Model


If we were creating a "quick and dirty" application that did everything simply by connecting everything in Interface Builder, we would not strictly need to create class les; everything could be inferred from the object model. But because we will need to create, refer to and manipulate instances of our classes in code, we will now proceed to generate class les for the entities that we have created in the object model. This is achieved, whilst in the class model designer, by going to the File menu and selecting the "New File" item. The rst of a series of dialogs appears, in which, we need to select the Managed Object Class item, before moving to the next stage:

Next, we need to specify which project and where in the project's folders we wish to place the les that will be generated; we will place the les in the Classes folder that we created in Finder. Finally, we need to select which entities, from the model, for which we want to create class les - we will select both the Language and Word entities.

The result will be header and implementation les which will appear in the Models group in Xcode; you can either leave them there or drag the les to the Classes group so that we can nd them along with all our other classes. The created les for the classes that should look something like the following:

Saving Files
Curiously, the Xcode IDE does not seem to provide a "Save All" menu item. Apart from saving the current le that you are editing, it would seem the only way to save other les, even ones you have previously edited but not saved, is to build the project. At which point, you will be prompted to save any unsaved les.

page 5

File Groups
When adding les to an Xcode project, it is not unknown for newly added les to appear in the wrong group in the IDE. This doesn't mean that they are in the wrong physical location on disk, merely that you may not have selected the right node in the "Groups and Files" pane of the IDE. If this should happen, simply drag the offending les to their correct position in the desired group.
// // // // // // //

Word.h Words Created by Joanna Carter on 11/12/09. Copyright 2009 Carter Consulting. All rights reserved.

#import <CoreData/CoreData.h> @class Language; @interface Word : { } NSManagedObject

@property (nonatomic, retain) NSString *text; @property (nonatomic, retain) Language *language; @end // // // // // // //

Word.m Words Created by Joanna Carter on 11/12/09. Copyright 2009 Carter Consulting. All rights reserved.

#import "Word.h" #import "Language.h" @implementation Word @dynamic text; @dynamic language; @end // // // // // // //

Language.h Words Created by Joanna Carter on 11/12/09. Copyright 2009 Carter Consulting. All rights reserved.

#import <CoreData/CoreData.h> @interface Language : { } NSManagedObject

@property (nonatomic, retain) NSString *name; @end

page 6

// // // // // // //

Language.m Words Created by Joanna Carter on 11/12/09. Copyright 2009 Carter Consulting. All rights reserved.

#import "Language.h" @implementation Language @dynamic name; @end

We shall discuss the layout and syntax of some Objective-C code further, when we come to declare the controller classes but, for now, you just need to know that the @dynamic declaration is a special implementation of a property that tells the compiler to look in the object model for the property accessors, rather than declaring them directly in the code.

Designing the Visual Interface


As we have previously discussed, unlike other development environments, such as Delphi or Visual Studio, Mac development encourages use of the MVC (Model View Controller) design pattern. This means that we have three different parts to our development process. We have already completed the design of the Model or classes; next we need to design the View and Controller components. Although the View and Controller components are separate, in that the View is designed in Interface Builder and the Controller is designed in Xcode, I have found that the process of designing of these two components is very much intertwined, with one Controller being specically designed for a particular View. The View or form appears as part of a XIB resource bundle, along with various other components that facilitate the linking of the View and its components to their related Controllers. Our next step is to start editing the View (form) by double-clicking on the MainMenu.xib le in the Resources folder of our project in Xcode; this will open the XIB le in Interface Builder, and we should see a window like this:

page 7

What's in a XIB Document?


So, we've opened our MainMenu.xib le in Interface Builder and found a document window with several objects in it:

but what do all the objects do?

File's Owner
This object usually represents the controller that will be responsible for managing connections between the controller class and any components in the XIB document; but, because this XIB document manages the main form of the application, in this case, the File's Owner is a proxy for the global instance of the NSApplication class, responsible for managing the entire application. In XIB documents other than for the main form, this object usually represents the controller class.

First Responder
Cocoa user interfaces use the Chain of Responsibility design pattern to handle input gestures, especially keystrokes. If focus is set to a control on the form, that control becomes the rst responder. But, if the control doesn't need to respond to a given keystroke, it will pass that keystroke on to its "Next Key View", which will, in turn, be responsible for either handling the keystroke or passing it on to its "next Key View". The First Responder object in XIB document can be regarded as a sort of "back stop", being the last in the chain to handle anything that nobody else wants to handle in this XIB document or its form.

Application
The Application object represents the global instance for the application and also acts as the First Responder does, but for the main application rather than for any secondary forms.

Window
The Window is where we layout any controls. If the window is not already showing, double-clicking on the object will show it.

MainMenu
The MainMenu object behaves in the same way as the Window, by providing a designer where we can add commands that will appear on the menu bar at the top of the screen. If the designer is not visible, double-clicking on the object will reveal it. All new XIB documents come with a default menu layout, which may be customised by adding and removing items to suit your particular application.

Words_AppDelegate
Because this XIB document contains the main form of the application, the File's Owner is already responsible as proxy for the global application object. So, in this case, we need a separate object for our controller class - this is that object.

Font Manager
This is responsible for responding to the Format menu. If you don't plan on letting users of you application format text, it can be deleted, along with the Format menu.

page 8

Before going any further, ensure that the the Inspector and Library tool windows are visible; these can be activated from the Tools menu of the Interface Builder IDE or from the Inspector button on the toolbar of the XIB document window. Further ensure that the "Objects" tab of the Library tool window is visible and that all the branches of the Cocoa section are expanded so that we can see all the sub-sections that will be mentioned in this tutorial. Double-clicking on the Window object in the XIB document window will show us our main form, to which we will only add a single button, for the moment, to allow us to open a secondary form, that will be used for editing the list of Languages that we can choose from whilst editing our list of Words in the main form.

All that is required, for now, is to drag a Push Button, from the Views & Cells section of the Library window, onto the form and set its Title property to "Languages" in the Attributes tab of the Inspector.

Connecting a Button to a Controller


Briey returning to Xcode, we now need to write a method in the Words_AppDelegate class that will be connected to our button by adding the following method declaration to the Words_AppDelegate.h le :
@interface Words_AppDelegate : NSObject { ... } ... - (IBAction) editLanguages:(id)sender; @end

and implement it in the Words_AppDelegate.m le:


- (IBAction) editLanguages:(id)sender { }

After building the project, to save these changes to the Words_AppDelegate class, we can now return to Interface Builder to connect our "Languages" button to this method.

page 9

Connections
With development tools like Visual Studio or Delphi, we would expect to double click on a button and have an event handler created, by the IDE, in the source code le for the form that we are designing. Interface Builder doesn't provide any such automatic mechanism for creating code; instead we would use Xcode to add a method, with the appropriate signature, to the Controller class and then use Interface Builder to make a Connection from the button to that method. The Apple documentation states that the return type for the method should be IBAction, which is functionally equivalent to void, otherwise Interface Builder will not be able to see the method. In actual fact, with the latest versions of Interface Builder, as long as you declare the method with the appropriate signature :
- (void) actionMethod:(id)sender;

it would appear that Interface Builder nds the method, even if you use void as the return type. Likewise, if we want our Controller class to be able to reference components, either in the XIB document window, or on a form, we would add a eld for each component, to which we want to refer, to the Controller class and, once again, use Interface Builder to make a Connection from the controller object to the appropriate component. Although Interface Builder doesn't look after automatically generating handler methods for buttons, it does make it easy to choose and connect to a method such as that which we have just provided.

If you hold down the Ctrl key whilst dragging the mouse from the "Languages" button (make sure you select the Button and not the Button Cell) to the Words_AppDelegate object, and then release the mouse, a popup list will allow you to choose which Action, from those available, to which you want to connect the button. In this case, choose the "editLanguages:" Action we just added to the Words_AppDelegate class. We shall be using this "Ctrl-drag" technique, throughout this example, to make various connections, so it may be useful to take a moment to familiarise yourself with how it works. If you want to know what connections have been made to an object, select the object in the XIB document window and choose the Connections tab on the Inspector, you will see a list of all connections that have been made to and from the selected object. You can also right-click or Ctrl-click on an object to see the same list of connections in a popup list.

page 10

Creating a Simple Browser Form


Before going any further with creating either the main form or other dialogs for editing our list of Words, we need to be able to create and manage a list of Languages, so that we can choose a Language for a newly created Word. So, we are going to create a very simple form and controller that will allow us to add, remove and edit Languages, with a minimum of code and interface design. Returning to Xcode, we need to select the Resources group of the project (where the MainMenu.xib le is found) and the use the File | New File menu item to select a Window XIB from the User Interface section of the New File dialog. Click on Next and name and place the new le as indicated in the following screenshot; using the English.lproj folder is a convention, adopted by the MainMenu.xib le created for us, as it makes it easier to internationalise your application if required; but that is not within the remit of this tutorial.

This will create a new XIB le - double-clicking on the le in Xcode will open it in Interface Builder. You will nd the created XIB document window appears much simpler than the MainMenu, but will need a couple of items adding to get everything working. In keeping with the MVC design pattern, we will also need to create a controller class for our Languages form. When we are dealing with forms, other than the main form of an application, a controller class is not automatically created, so we will have to do this manually.

You will notice an item in the XIB document window called File's Owner; it is this object that will represent an instance of the controller class that we are about to create and which will allow us to connect UI components to the Controller.

page 11

Creating a Window Controller


Return to Xcode, select the Classes group and, once again, go to the File | New File dialog but, this time, select an Objective-C class that is a subclass of NSWindowController because this already has some base functionality that we can use, as well as being able to extend it for our own purposes.

Naming the le LanguagesWindowController will give us a pair of les, which should be located in the Classes folder of the project. Editing these les to resemble the following will give us an opportunity to discuss some of the Objective-C syntax as we design this window controller.
// // // // // // //

LanguagesWindowController.h Words Created by Joanna Carter on 15/12/09. Copyright 2009 Carter Consulting. All rights reserved.

#import <Cocoa/Cocoa.h> @interface LanguagesWindowController : NSWindowController { NSManagedObjectContext *managedObjectContext; } IBOutlet NSArrayController *languagesController;

- (LanguagesWindowController *) initWithContext:(NSManagedObjectContext *)context; @end

The line that starts with @interface is the rst line of the declaration of the class type, including the base class that it derives from. Anything declared between the curly braces is regarded as an instance variable or eld. In our example, we declare an NSManagedObjectContext eld, which is going to hold the context passed into the constructor. When the LanguagesWindowController is created, we will pass the Managed Object Context from the main form controller, allowing us to add, remove and edit items to and from the same context or storage mechanism as the rest of the application.

page 12

If we want to be able to access any of the components, in the XIB le, from the Controller class, we need to add a specially marked eld to the controller class and connect up that eld to the component in Interface Builder. The NSArrayController eld is a reference to the array controller that we will have to add to the XIB le that will supply a Table View, which we will also be adding to the XIB le, to allow us to edit our list of Language objects; it is marked with the IBOutlet marker to make it visible to Interface Builder. The initWithContext method is an initialiser and is marked with a minus sign to indicate that it is an instance method, rather than a class or static method (which would be marked with a plus sign).
// // // // // // //

LanguagesWindowController.m Words Created by Joanna Carter on 15/12/09. Copyright 2009 Carter Consulting. All rights reserved.

#import "LanguagesWindowController.h" @implementation LanguagesWindowController - (LanguagesWindowController *) initWithContext:(NSManagedObjectContext *)context; { self = [super initWithWindowNibName:@"LanguagesWindow"]; managedObjectContext = context; [self setShouldCloseDocument:NO]; } return self;

We start implementing the initialiser by calling the base initialiser, declared in the NSWindowController class, which is responsible for creating the form, the array controller and all the rest of the resources in the XIB le. Then, we hold onto the managed object context and tell the base controller class that closing the form should not close the application, nally returning "self" as the newly initialised object.
- (void) awakeFromNib { NSSortDescriptor *sortDescriptor = [NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; } [languagesController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

Simply redeclaring a method in the implementation overrides that method. The awakeFromNib method gets called after all the components in the XIB le have been created and is the rst opportunity we get to initialise them. All we are doing here is creating and applying a sort descriptor, to the languagesController, to ensure that the Languages in our list are sorted, in ascending order, based on the "name" property.
- (void) windowWillClose:(NSNotification *)notification { NSError *error; if (![managedObjectContext save:&error]) { // handle error }

@end

Finally, we declare the windowWillClose delegate method, which will get called when the form closes, to ensure that any changes made to the managed object context get written to the persistent storage.

page 13

Connecting the Languages XIB le to its Controller Class


Now that we have a window controller class written, we can use Interface Builder to design the form and connect its UI components to the window controller. Don't forget to build the project in Xcode before switching to Interface Builder, otherwise the designer will not be able to see the changes you have made to the controller class. We start by selecting the File's Owner object and selecting the Identity tab in the Inspector, so that we can set the Class property to our LanguagesWindowController class.

Specifying the controller class allows Interface Builder to know what Outlets (elds) and Actions (event handlers) are available for connection to components on the form but, before adding UI components to the Languages window itself, and having chosen the File's Owner class, we now have to connect the various other components, within the XIB document window, to and from the LanguagesWindowController class, represented by File's Owner object; this is simplied by being able to Ctrl-drag connections from one object to another: 1. Ctrl-drag a connection from the File's Owner to the Window object and select the "window" Outlet 2. Ctrl-drag a connection from the Window to the File's Owner object and select the "delegate" Outlet

Adding an NSArrayController
Having specied an NSArrayController outlet in the LanguagesWindowController class, we need to add an NSArrayController object to the XIB document window by dragging it from the Library window's "Objects & Controllers" section.

Array controllers represent a list of objects of a given type and are capable of managing all the interactions between controls that not only display the objects, but also controls like buttons, that interact with the list, adding and deleting items etc.. For this example, select the Identity tab of the Inspector and set the Name property to "Languages Controller"

page 14

Ensure that the array controller object is still highlighted and select the Attributes tab in the Inspector. Because we are using Core Data to provide our objects, we need to select "Entity" for the Mode property, enter "Language" as the Entity Name, and check "Prepares Content", "Uses Lazy Fetching" and, because we want to add and remove the items to/from the list, "Editable". You should also check the "Auto Rearrange Content" option; the other options will affect the behaviour of the control that is to display our list and are left to you, dear reader, to experiment with to see what effect they have. Because we are using Core Data to provide data to the Languages Controller, we need to inform the controller from where it is going to get its data. On the Bindings tab of the Inspector for the Languages Controller, we need to bind the Managed Object Context, in the Parameters section, to the File's Owner, entering "managedObjectContext" as the Model Key Path. This will connect to the managedObjectContext eld that we added to the LanguagesWindowController class. Finally, to connect the array controller to the eld in the controller class, Ctrl-drag a connection from the File's Owner object to the Languages Controller object and select the "languagesController" Outlet.

Designing the Languages Window


The design of the Languages form is very simple; the only controls we need will be a Table View and two Square Buttons:

page 15

Tabbing from One Control to Another


Although, in this form, it is not too important to enable tabbing from one control to another I will take this opportunity to describe how Cocoa handles the subject. Cocoa relies on the "Chain of Responsibility" design pattern to decide which control should be responding to keyboard input. Every Nib document window contains a First Responder object, which can be regarded as the "back stop". In other words, if nothing else on the form responds to a particular keystroke, the First Responder is the last link in the chain of responsibility for a given keystroke or menu item. It can also give us an opportunity to handle menu items that, ordinarily, would be ignored. When you use text entry elds on a form, moving from that control by hitting the Tab or Enter key sets the focus to the next control in the responder chain - but only if the "nextKeyView" outlet is connected to the next control in the responder chain. Failure to set the nextKeyView on a text eld means that, if you use the mouse to press a button or click on anything other than another text eld style of control, focus will remain in the edited control and the underlying value will never get updated.

Setting the Tab Order


To set a control as the initial responder on a form, click on the caption bar of the form and Ctrl-drag to the desired control, selecting "initialFirstResponder" on the popup dialog. To set the next responder in the chain, simply Ctrl-drag from one control to the next, selecting "nextKeyView" on the popup dialog. If you decide you don't want to setup a tab order, you should ensure that the controller method, that is responsible for closing the form, tells the form to make itself the rst responder, thus updating the value behind any uncommitted edits to text elds by removing rst responder status from that control.

The Table View


Although the component we have dropped on the form appears to be just a Table View, it is actually a Table View within a Scroll View. Click on the component once and you should see the title bar of the Inspector change to reect the fact that you have correctly selected the Scroll View. should you click more than once, you will select, rst the Table View and then one of the Table Columns. There is an easier way to select any of the "layers" of a control such as the Table View; either Ctrl-Shift-click or Shift-right-click on the control and select from the popup list that appears. But, to start with, we can congure the Scroll View so, if you have already selected either the Table View or a column, simply click anywhere else on the form and then re-click, once, to select the Scroll View. For this example, we don't need to do anything here but it is left to the reader to experiment with the various options available. Next select the Table View, by clicking once more on the Scroll View, or selecting from the popup list, and you should see the component change appearance slightly and the caption in the Inspector will change.

page 16

Make sure that there is only one column and that the Headers are visible; you might nd that the headers don't redraw correctly after changing the number of columns, if you uncheck and recheck the "Headers" option, this should correct itself. Click once more on the Table View and you should see the selection change to show Table Column properties in the Inspector.

All that is required, in the Attributes tab of the Inspector, is to set the Title of the column to "Language". Checking the "Editable" option will allow us to edit the values directly in the cells of the column. It may not be the most sophisticated method of editing but it serves to demonstrate just how little effort you have to go to to setup a simple, working application. In order to connect the column to a source of data, we need to select the Bindings tab of the Inspector and expand the Value section. For our example, select the "Languages Controller" for the "Bind to" property, "arrangedObjects" for the "Controller Key" property and enter "name" for the "Model Key Path" property.

page 17

Conguring the Add and Remove Buttons


The two buttons on this form are for adding and removing items from the list of Languages and can be congured to do their job without a single line of code. Click on one of the buttons and select the Attributes tab in the Inspector.

All that is needed to add "+" and "-" signs to our two buttons is to select either NSAddTemplate or NSRemoveTemplate for the Image property.

If you Ctrl-drag the mouse from one of the buttons to the Languages Controller, you will see a list of possible Actions that are available on an array controller. Select "add:" for the button with the "+" sign and "remove:" for the button with the "-" sign. Although that is all there is to enabling adding and removing items from a list, the "-" button will always be active, even if there are no items selected or even if the list is empty. In order to ensure that the buttons are only enabled appropriately, go to the Bindings tab of the Inspector for the button and set the Controller Key property of the Enabled section to "canAdd" for the "+" button and "canRemove" for the "-" button. So, now we should have a complete form that can show, edit, add and remove items from a list of Languages.

page 18

Showing the Languages Window


Returning to Xcode, we now need to write the method in the Words_AppDelegate class that we have connected to the "Languages" button on the main form and that will open the Languages Window. Import the header le for our controller class and add the following code to the "editLanguages" method in the Words_AppDelegate.m le:
... #import "LanguagesWindowController.h" @implementation Words_AppDelegate ... - (IBAction) editLanguages:(id)sender { LanguagesWindowController *controller = [LanguagesWindowController alloc]; [controller initWithContext: self.managedObjectContext]; } [controller showWindow:nil];

In theory, all being well, you should now be able to run the application, click on the "Languages" button and add, remove and edit Languages. As long as you close the Languages window beforehand, if you quit the application and restart it, you should have the same list of Languages that you edited previously.

Preparing the Main Form


Using a Table View and two buttons to totally manage our list of Language objects might be ne for such a simple, one attribute, class but, if we need to edit more complex objects, we are going to have to create a form that allows us to edit an object that has too many attributes to t in a Table View. The two main styles of forms available to us are: modal, where the user is prevented from further activity with the rest of the application until the modal form is closed; and non-modal, where the user can switch between any forms that are open within the application. For the purposes of this example, we are going to create a modal form that will show when we want to add a new Word and a non-modal form for editing existing objects. But before we start work on the editing forms, we need to provide a means of browsing Word objects in the main form of our application.

Preparing the Controller


We need two additional elds adding to the Words_AppDelegate class that was created for us when we started this project: an Outlet for an Array Controller that will provide the data to the list of Words that will appear on the main form, and a eld to hold a Sort Descriptor that will maintain our list in alphabetical order.
@interface Words_AppDelegate : NSObject { ... IBOutlet NSArrayController *wordsController; } NSArray *sortDescriptors;

We also need to implement the awakeFromNib method in the Words_AppDelegate.m le :


- (void) awakeFromNib { NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"text" ascending:YES]; } [wordsController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

page 19

Preparing the XIB File


Switch to Interface Builder and drag an NSArrayController object, from the "Objects & Controller" section of the Library, onto the MainMenu.xib document window. Select the Identity tab of the Inspector and set the Name of the array controller to "Words Controller". Change to the Attributes tab of the Inspector, set the Mode to "Entity" and enter "Word" as the Entity Name. Ensure that the "Prepares Content", "Uses Lazy Fetching" and "Auto Rearrange Contents" options are checked. In the Parameters section of the Bindings tab, bind the Managed Object Context property to the Words_AppDelegate object, setting the Model Key Path to "managedObjectContext". Ctrl-drag a connection from the Words_AppDelegate object to the Words Controller object and select "wordsController" as the outlet. Now that we have a source of data for the controls on the form, we can start to setup the controls that will display and manage our list of Word objects.

Setting up the Main Form


The process of providing a Table View and "+" and "-" buttons is similar to that which was required for our Languages form; the only real differences being that we have two columns in the Table View and that we should not connect the "+" button to the array controller; instead, we shall use that button to show a modal "New Word" dialog. Now that we have a source of data for the controls on the form, we can double-click on the Window object to display the form, and add an NSTableView to it, along with two Square Buttons and an "Edit" Push Button, arranged as in the following screenshot.

Setup the Table View as in the above screenshot of the Inspector, with two columns instead of the one that we needed for the Languages form.

page 20

Select the columns in turn and set the "Title" property of the left column to "Text" and the "Title" property of the right column to "Language". We are not going to allow editing in this Table View so we need to ensure that the Editable option is unchecked for both columns.

Connecting the Columns to the Array Controller


Just as we did with the Languages Table View, we now need to bind the columns to the relevant properties in the array controller. Select the Bindings tab on the Inspector and setup both columns in a similar manner to the following screenshot. The only difference between the binding for the Text column and the Language column (shown here) is that the Model Key Path should contain "text" instead of the dotted "language.name" path.

Conguring the Add and Remove Buttons


Setup the "+" and "-" images and bindings just as you did for the Languages form. We only need to Ctrl-drag a connection from the "-" button to the Words Controller, just as you did with the Languages Controller in the Languages form; leave the "+" button, we shall connect that up after we have created the New Word modal dialog.

page 21

Creating a Modal Dialog


The Cocoa frameworks provide an interesting variation on modal dialogs, in that, instead of the dialog appearing in front of, and separate to, the current window, the dialog slides down from the caption bar of its parent window, as in the following screenshot:

This approach means that, even if the parent form of the dialog is the main form of the application, the modal nature of the dialog only disables the parent form; all other forms in the application remain useable.

Creating the Controller


Select the Classes folder in the Xcode project and either right-click or go to the File menu and select "New File". Because we are going to use an NSPanel instead of an NSWindow, we do not need to derive this controller from NSWindowController. Instead, we just derive from NSObject, naming the class "NewWordSheetController".

page 22

The newly created header le needs several elds, one property and three methods adding, as in the following code :
// // // // // // // NewWordSheetController.h Words Created by Joanna Carter on 16/12/09. Copyright 2009 Grandes Images. All rights reserved.

#import <Cocoa/Cocoa.h> @interface NewWordSheetController : NSObject { NSManagedObjectContext *managedObjectContext; IBOutlet NSArrayController *sourceListController; IBOutlet NSObjectController *wordController; IBOutlet NSArrayController *languagesController; IBOutlet NSWindow *parentWindow; } IBOutlet NSPanel* panel;

@property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext; - (IBAction) create:(id)sender; - (IBAction) save:(id)sender; - (IBAction) cancel:(id)sender; @end

The "managedObjectContext" eld allows us to hold on to a separate context to the main form; this means that any edits in the New Word dialog will not affect the list of Words in the main form until we have conrmed that we really want the newly created Word by closing the dialog with the Save button. The "sourceListController" eld holds onto the original list, so that we can access it when we need to add the newly created Word. The "wordController" manages the bindings between a temporary Word, that we will use for editing, and the controls on the dialog. The "languagesController" supplies the list of Languages to the Popup Button that chooses the Language for the Word. The "parentWindow" is required so that we can specify it as the form upon which the dialog will appear. the "panel" is the dialog itself. The implementation le for this class will need to include :
#import "Word.h" #import <objc/runtime.h>

to allow us to reference the Word class in the create: method, and to give us access to the reection APIs that we will be using to copy property values in the sheetDidEnd:returnCode:contextInfo: method. We can start eshing out the implementation by writing the getter method for the "managedObjectContext" property where we will lazily create the context, before moving on to the three methods that will create the dialog and respond to its "Save" and "Cancel" buttons.

page 23

- (NSManagedObjectContext *) managedObjectContext { if (managedObjectContext == nil) { managedObjectContext = [[NSManagedObjectContext alloc] init]; NSManagedObjectContext *sourceContext = [sourceListController managedObjectContext]; NSPersistentStoreCoordinator *store = [sourceContext persistentStoreCoordinator]; } } [managedObjectContext setPersistentStoreCoordinator:store];

return managedObjectContext;

It is important to note that, although we are created a secondary, isolated, context, it must still use the same Persistent Store Coordinator as the main form so that, even though any changes are isolated from the main context, we are still talking to the same, underlying, data. This code creates the secondary context and connects it to the same Persistent Store Coordinator as the main context.
- (IBAction) create:(id)sender { if (panel == nil) { NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSNib *nib = [[NSNib alloc] initWithNibNamed:@"NewWordSheet" bundle:bundle]; BOOL success = [nib instantiateNibWithOwner:self topLevelObjects:nil]; if (success !=YES) { // should present error return; }

} else { [panel makeFirstResponder:panel]; } Word *newWord = [wordController newObject]; [wordController setContent:newWord]; [NSApp beginSheet:panel modalForWindow:parentWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];

Although, every time we display this dialog we will be editing a new Word object, the NSPanel can be reused once it is created; so the rst part of the "create:" method checks if the panel already exists and, only if this is the rst time the method has been called, does it actually create the dialog, otherwise it simply ensures that input focus will be on the rst edit when the dialog appears. Subsequent calls will only have to create a new temporary Word object and show the dialog. Because we are using Core Data objects, we must obtain any new objects from the Managed Object Context instead of calling the normal style of constructor. NSObjectController and NSArrayController both provide a convenience method that can provide us with a new instance of the type that they are controlling but we have to be aware that, although the controller created an instance and passed it to us, it didn't add the object to itself; hence the subsequent call to "setContent:" passing nil as the optional sender parameter

page 24

Objective-C Method Names and Parameters


Unlike languages where methods are dened with an identier followed by a parenthesised parameter list, Objective-C uses named parameters as part of the method name. Thus, a method that would be written in code as :
(void) setText:(NSString *)text withColor:(NSColor *)color { }

would be referred to as setText:withColor: Note that the colons are an important part of the method name and should always be included when referring to such methods.

Selectors
If you have programmed in C# and come across delegate methods, or in Delphi an come across method pointers, then you will be familiar with the concept of a selector in Objective-C. Selectors are held in variables of type SEL and, to obtain the "address" of a method, you use the syntax :
SEL aSelector = @selector(aMethod:withParameter:)

SEL references can be passed to methods just as you would expect with other types of function pointers or delegates. To complete this method, we need to pass a delegate method to the "didEndSelector:" parameter of NSApp's beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo: method, and the following code demonstrates the implementation of such a delegate method :
- (void) sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *) contextInfo { // we are only interested in doing something with the edited word // if the Save button was pressed if (returnCode == NSOKButton) { NSError *error; // retrieve the Word that has been edited id editingWord = [wordController content]; // attempt to validate edited Word if ([editingWord validateForUpdate:&error]) { // create a new Word from the original context NSManagedObject *newWord = [sourceListController newObject]; // get runtime info so that we can copy property values from // temporary editing object to newly created one Class wordClass = [Word class]; unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList(wordClass, &propertyCount); // iterate through properties for (unsigned int i = 0; i < propertyCount; i++) { objc_property_t property = properties[i];

page 25

const char *cPropertyName = property_getName(property); // convert c-style string to NSString for ease of use NSString *propertyName = [NSString stringWithCString:cPropertyName encoding:NSUTF8StringEncoding]; id propertyValue = [editingWord valueForKey:propertyName]; // if the property value of edited object is a managed object, // we must use its managedObjectID to get the same object from // the original context that will hold the newly created Word if ([propertyValue isKindOfClass:[NSManagedObject class]]) { NSManagedObject *managedObject = propertyValue; NSManagedObjectID *managedObjectID = [managedObject objectID]; NSManagedObjectContext *slCntxt = [sourceListController managedObjectContext]; // replace value from editing context with object from original context propertyValue = [slCntxt objectWithID:managedObjectID];

// assign value from editingWord to newWord [newWord setValue:propertyValue forKey:propertyName];

// add the new Word to the original list [sourceListController addObject:newWord]; // attempt to save the changes made to the original context if (![[sourceListController managedObjectContext] save:&error]) { // should handle any errors here } else { // save succeeded so call fetch: to refresh the list and update sort order // (pass nil as the sender because the parameter will not be used) [sourceListController fetch:nil]; } } }

// close the dialog [sheet orderOut:self]; // rollback any changes to the temporary context [self.managedObjectContext rollback]; // clear temporary object from controller [wordController setContent:nil];

We will also need two methods, with a return type of IBAction, to handle the Save and Cancel buttons on the dialog :
- (IBAction) save:(id)sender { [panel makeFirstResponder:panel]; } [NSApp endSheet:panel returnCode:NSOKButton];

- (IBAction) cancel:(id)sender { [NSApp endSheet:panel returnCode:NSCancelButton]; }

page 26

The save: method starts by telling the panel to make itself the rst responder - this removes focus from the text eld, thus ensuring that any changes made in the text eld are applied; but apart from that, all that is required of these two methods is to tell the App to close the dialog, passing an appropriate return code for the button that was pressed. Finally, for this controller, we can override the awakeFromNib method to ensure that the Languages in the Popup Button's list will be sorted in alphabetical order.
- (void) awakeFromNib { NSSortDescriptor *sortDescriptor = [NSSortDescriptor alloc]; [sortDescriptor initWithKey:@"name" ascending:YES]; } [languagesController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

Don't forget to save and build the les we have just been working on; we will need to be able to see all the necessary outlets and actions to complete the XIB le

Creating the XIB le


Unlike the process of creating a new form, which is based on an NSWindow, this style of modal dialog is based on an NSPanel and, because there is no "New File" template for an NSPanel-based XIB, we need to create a new "Empty XIB".

We will call this le NewWordSheet.xib and will place it, along with our other XIB les, under the English.lproj folder. This will give us a XIB document window that looks like the following screenshot:

The visible part of this XIB le will be an NSPanel, which can be found in the Windows section of the Library window; simply drag an NSPanel from the Library to our new XIB document window.

page 27

To complete the items that we will require for this dialog, drag an NSObjectController and an NSArrayController from the Objects & Controllers section of the Library, naming them "Word Controller" and "Languages Controller" respectively. Before moving on to designing the form, we need to select the File's Owner object and use the Identity tab of the Inspector to set the File's Owner object to be an instance of our NewWordSheetController class.

Now that the File's Owner class is set, we need to make connections between it and the other objects in the XIB for which it has outlets : 1. Ctrl-drag a connection from the File's Owner to the Panel object and select the "panel" Outlet 2. Ctrl-drag a connection from the Panel to the File's Owner object and select the "delegate" Outlet 3. Ctrl-drag a connection from the File's Owner to the Word Controller and select the "wordController" Outlet. 4. Ctrl-drag a connection from the File's Owner to the Languages Controller and select the "languagesController" Outlet.

Setting up the Word Controller


On the Bindings tab of the Inspector for the Word Controller, we need to bind the Managed Object Context, in the Parameters section, to the File's Owner, entering "managedObjectContext" as the Model Key Path. This will connect to the managedObjectContext eld that we added to the NewWordSheetController class. Move to the Attributes tab of the Inspector an select "Entity" for the Mode property and "Word" as the Entity Name. Unlike the Array Controllers that we have setup so far, this Object Controller isn't going to fetch its object from storage, via the Managed Object Context, automatically. Because we are only going to create the XIB once, we will be creating a temporary Word object each time we want to show the dialog and destroying it after we have nished with it. Therefore, we must ensure that the "Prepares Content" and "Uses Lazy Fetching" options are not checked, otherwise the controller will try to create its own content object, which will interfere with how we handle our temporary object.

page 28

Setting up the Languages Controller


To setup the Languages Controller, follow the same procedure that we used for the Languages Controller for the Languages form.

Setting up the Dialog


Before we place any controls on our panel, we must ensure that one very important property of the panel is correctly set. Select the Panel object in the XIB window and, in the Attributes section of the Inspector, uncheck the "Visible At Launch" option; failure to do this will mean that the dialog will not appear from its correct place under the caption bar of its parent form, instead, it will appear as a free-oating, non-modal, dialog, somewhere on the screen and will not prevent further interaction with its parent form. The Word business class that we are using for this example is extremely simple. All that is needed is to drag: two Labels, one Text Field and one Pop Up Button from the Inputs and Values section of the Library; and two Push Buttons from the Buttons section. Layout the controls in a similar manner to the screenshot on the right, select the Attributes tab from the Inspector and we can start setting up the visual appearance of the controls. Starting with the Labels, set the Title of the top one to "Word:" and of the bottom one to "Language:"; then set the Alignment of both to be right-aligned. The Text Field can be left as is but we can remove the Title from the Pop Up Button. Apple's Human Interface Guidelines state that buttons for dismissing dialogs should be positioned at the bottom right of a dialog and that the "Action" button should be the rightmost of those buttons. The "Action" button is the one that completes the action for which the dialog was shown. It is not necessarily the default button although, in our example, it is. A default button is dened as the one least likely to destroy or lose any data. The "Cancel" button should always appear immediately to the left of the "Action" button. Setting up the visual appearance of the Push Buttons not only alters their appearance, it also affects their behaviour. Let's go through setting up our two buttons : Selecting one of our buttons, on the Attributes tab of the Inspector, we need to specify the Title property and then click in the empty box for the Key Equiv. property until the focus switches to that box; the next key that you press will be assigned to the button as its shortcut key. For the Cancel button, press the Esc key and, for the Save button, press the Enter key. You will notice that pressing Enter for the Save button not only assigns the key as a shortcut, it also changes the colour of the button to blue; when the dialog is actually in use, this button will also adopt the standard Aqua behaviour of gently pulsing. So, having setup the visual appearance of our controls, we can now move on to connect them to their relevant controllers.

Connecting the Buttons


All that is required to connect the two buttons to the methods we have created for them in the Controller is to simply Ctrl-drag a Connection from the Cancel button to the File's Owner object and select the "cancel:" action; likewise Ctrl-drag from the Save button, selecting the "save:" action.

page 29

Binding the Controls


Connecting the Text Field is relatively easy. Click on the Text Field and move to the Bindings tab of the Inspector and expand the Value section. You will need to bind to Word Controller, then set the Controller key to "selection"; this means that the Text Field will be bound to the object that is the current selection of the Controller - in the case of this Object Controller, this will point to the instance of our Word class which will be held as the Content object of the controller (in the case of an Array Controller, this would be the currently selected object from the list held in the controller). The Model Key Path should be set to the "text" property of our Word object. The only options that would possibly be relevant are "Continuously Updates Value" and "Validates Immediately"; these both determine whether the value being entered in the Text Field is passed to the property of the object to which the Text Field is bound, and validated, after every key stroke, or whether the value is only passed and validated when the user attempts to quit the Text Field. Connecting the Pop Up Button control is a little more complicated and involves no less than three bindings : the Content, the Content Values and the Selected Object. Click on the Pop Up Button and select the Content section of the Bindings tab in the Inspector (if the Bindings tab appears with some of the sections already expanded, I would recommend that you collapse all sections except the one you want to work on).

The Content and Content Values bindings are both involved in providing items to the popup list that will present our list of Languages so that the user can choose one therefore, they are both bound to the Languages Controller. The Content binding, which binds to the whole list of Language objects, only needs to set the Controller Key to "arrangedObjects" but the Content Values binding, which binds to the value of the property that is to be displayed, from each Language object in the list, also needs to set the Model Key Path to "name"; this will be the property whose values will be visible in the items of the popup list. To complete the binding of our Pop Up Button, we need to bind the Selected Object to the "language" property of the "selection" from our Word Controller.

page 30

Activating the New Word Dialog from the Main Form


When we needed to activate the Languages form from the main form, we wrote code in an Action method, that created an instance of the LanguagesWindowController class and told it to show the form. However, we can use an alternative approach for the NewWordSheetController class; we can get the XIB le that holds the main form to automatically instantiate it for us. Select the MainMenu.xib le from XCode, open it and drag a NSObject from the Objects & Controllers section of the Library window. Move to the Identity tab of the Inspector and choose "NewWordSheetController" as the Class; this will automatically rename the object to "New Word Sheet Controller".

Because the controller will be instantiated as soon as the XIB le is loaded, making the necessary connections to activate the form couldn't be easier : 1. Ctrl-drag a connection from the New Word Sheet Controller to the Window object and select the "parentWindow" Outlet 2. Ctrl-drag a connection from the New Word Sheet Controller to the Words Controller object and select the "sourceListController" Outlet 3. Ctrl-drag a connection from the "+" button on the form to the New Word Sheet Controller and select the "create:" Action. If you save these changes to the XIB le, return to Xcode and build and run the application, you should now be able to click on the "+" button and add new Word objects to the list on the main form.

page 31

Creating an Editing Form


So far, in this example, we have provided a main form that allows the browsing of a list of Words, a modal dialog for adding new Words and a separate form for managing a list of Languages. The one thing we cannot yet do is to edit the Words from the list. For the purposes of this example, we shall be creating a non-modal version of the modal dialog that we used for adding Words; so we will start by creating the controller class.

Creating the Controller


The new controller class is created in exactly the same way as the LanguagesWindowController; just use EditWordWindowController as the class name.
// // // // // // //

EditWordWindowController.h Words Created by Joanna Carter on 07/01/10. Copyright 2010 Carter Consulting. All rights reserved.

#import <Cocoa/Cocoa.h> #import "Word.h" @interface EditWordWindowController : NSWindowController { NSManagedObjectContext *sourceManagedObjectContext; NSManagedObjectContext *managedObjectContext; Word *editingWord; IBOutlet NSObjectController *wordController; } IBOutlet NSArrayController *languagesController;

- (EditWordWindowController *) initForWord:(Word *)word withManagedObjectContext:(NSManagedObjectContext *)context; - (IBAction) save:(id)sender; - (IBAction) cancel:(id)sender; @end

In the same way that we used a secondary Managed Object Context to ensure that new Word objects didn't get added to the list on the main form, whilst we were editing them, this controller also needs to supply such a secondary context. The elds added to this class are : The "sourceManagedObjectContext" eld allows us to hold on to the original context, so that we can apply the changes made if the user presses the Save button. The secondary "managedObjectContext" eld will be used whilst editing the selected Word. The "editingWord" to hold onto the temporary Word object we shall be using for editing. The "wordController" manages the bindings between the temporary Word, that we will use for editing, and the controls on the dialog. The "languagesController" supplies the list of Languages to the Popup Button that chooses the Language for the Word. The "initForWord:withManagedObjectContext:" method is an initialiser will take the selected Word and the original Managed Object Context as parameters, and the "save:" and "cancel:" methods will be connected to their respective buttons on the form.

page 32

- (EditWordWindowController *) initForWord:(Word *)word withManagedObjectContext:(NSManagedObjectContext *)context { self = [super initWithWindowNibName:@"EditWordWindow"]; sourceManagedObjectContext = context; if ([sourceManagedObjectContext hasChanges]) { NSError *error = nil; } [sourceManagedObjectContext save:&error];

managedObjectContext = [[NSManagedObjectContext alloc] init]; [managedObjectContext setPersistentStoreCoordinator:[context persistentStoreCoordinator]]; editingWord = (Word *) [managedObjectContext objectWithID:[word objectID]]; [self setShouldCloseDocument:NO]; } return self;

The initialiser starts off by calling the inherited initialiser, ensuring the source context is up to date and then taking hold of the the source context and creating the editing context from the same storage mechanism. We use the "objectID" property, of the Word that was passed in, to retrieve the same Word from the editing context but, unlike with the NewWordSheetController, we cannot set this Word from the editing context to be the Content object of the Word Controller because, at this point, the XIB le hasn't been fully loaded. Instead, we hold the Word in the eld, declared in the header le, until the AwakeFromNib method gets called.
- (void) awakeFromNib { [wordController setContent:editingWord]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor alloc]; [sortDescriptor initWithKey:@"name" ascending:YES]; } [languagesController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

Apart from setting the Content object of the editing controller, we also create the usual Sort Descriptor to ensure that the Languages in the Pop Up Button are always in the same order.
- (IBAction) save:(id)sender { [self.window makeFirstResponder:self.window];

The "save:" method starts by calling "makeFirstResponder:"; this ensures that focus is removed from the currently active control and that all values from the controls have been assigned into the edited object. Because we are using a secondary Managed Object Context to manage the edited Word, we have to be aware that any changes that are saved to the secondary context will not be seen, in controls connected to the main context, until those changes have been merged into the main context. Furthermore, such saves to a context are not guaranteed to have completed by the time the next line of code is executed, therefore we need to ask the default Notication Center to observe when the editing context has nished saving and to call our designated delegate method.

Anonymous Methods
As of Snow Leopard, Objective-C provides the language feature, variously known as closures, blocks or anonymous methods, which allows us to declare and pass a delegate method, at the same time, instead of passing, what would otherwise have been, a selector pointing to a separately declared method. The "name" of an Objective-C block (an anonymous method) is represented by a "^", followed by any arguments, in parentheses, followed by the method body.

page 33

NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; [dnc addObserverForName:NSManagedObjectContextDidSaveNotification object:managedObjectContext queue:nil usingBlock:^(NSNotification *notification)

[sourceManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; }]; NSError *error; if (![managedObjectContext save:&error]) { // Update to handle any error appropriately. } [dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:managedObjectContext]; } [self.window performClose:sender];

When the delegate method is called, we just call the "mergeChangesFromContextDidSaveNotication:" method on the original context to merge the changes made in our secondary context. After the call to "save:" on the secondary context, we can then safely remove the notication and close the form.
- (IBAction) cancel:(id)sender { [self.window makeFirstResponder:self.window]; [managedObjectContext rollback]; } [self.window performClose:sender];

The "cancel:" method is much simpler. All that is required is to ensure that focus is removed from any controls, rollback any changes made and close the edit form. And that concludes the code for the EditWordWindowController class. Now we shall move on to creating the form that will allow users to edit Word objects.

Creating the XIB le


Create a new Window XIB le, naming it EditWordWindow.xib and saving it in the same English.lproj folder as all our other XIB les.

page 34

To simplify the creation of the form, we can select all the controls from our NewWordSheet form and copy them onto this form; the only visible difference between the two forms should be the size of the caption bar, which is slightly taller for the Window than for the Panel.

Set the File's Owner object's Class to "EditWordWindowController" and add an NSObjectController and an NSArrayController to the XIB document window and name them as "Word Controller" and "Languages Controller" respectively. The Word Controller should be setup just as we did for the NewWordSheet XIB le. Setup the Languages Controllers in exactly the same way as for the Languages form. When you make the connections between the objects in the XIB document window, where you see "panel" in the instructions, simply replace that with "window". Finish off by connecting the controls and buttons on the form to their respective objects in the XIB document window, just as we did with the NewWordSheet XIB le, and you should now have a completed editor form.

Showing the Edit Word Window


Returning to Xcode, we now need to write the method in the Words_AppDelegate class that we will connect to the "Edit" button on the main form and that will open the Edit Word Window.
#import "Word.h" ... - (IBAction) editWord:(Word *)wordToEdit;

The method declaration gives a hint that this Action method (which we can think of as a ButtonClick event handler) doesn't look like anything we would be used to in either Visual Studio or Delphi. Instead of having the usual "sender" parameter, this method is expecting an instance of our Word class.
#import "EditWordWindowController.h" ... - (IBAction) editWord:(Word *)wordToEdit { if ([self.managedObjectContext hasChanges]) { NSError *error = nil; if (![self.managedObjectContext save:&error]) { NSLog(@"Can't save edited language"); } }

page 35

EditWordWindowController *editWordWindowController = [EditWordWindowController alloc]; [editWordWindowController initForWord:wordToEdit withManagedObjectContext:[self managedObjectContext]]; } [editWordWindowController showWindow:nil];

You can see, from the code in the implementation, that the Word parameter gets passed to the initialiser for the controller but, coming from a Visual Studio or Delphi background, the idea of having an event handler for a button click that can take a parameter other than the "sender" button that triggered the event, is going to be something of a novelty. The reason for for this unusual design is because Cocoa includes a feature known as Action Invocation bindings, which allow us invoke an Action method, passing parameters at the same time, instead of just making a "dumb" connection between a button and its Action method, with a "sender" argument, which is usually the button. So, let's move on to hooking up the "Edit" button to this method of the controller. Open the MainMenu.xib le in Interface Builder, click on the "Edit" button on the form and, instead of simply Ctrl-dragging a connection from the button to the controller, select the Bindings tab on the Inspector and expand the Argument binding in the Action Invocation section.

The Argument binding can be regarded as the sender or source of the invocation; the source of the argument we want to pass to the Action method should reect the currently selected item in the list of Words we are browsing in the Table View. We specify this object by binding to the Words Controller and setting the "Controller Key" property to "selection". Setting the "Model Key Path" property to "self" means that we are going to use the "self" object that is the current selection (this seems like a peculiar naming convention but it does give us the currently selected Word). The "Selector Name" property needs setting to the name of the method, in the controller, that we want to invoke, in the case of our example, "editWord:". The second half of the Action Invocation binding species the Target of the invocation; in other words, the object that contains the method to be invoked. We need to bind to the "Words_AppDelegate" and specify "self" for the "Model Key Path" property. The "Selector Name" property must be be set to the same name as for the Argument binding. Don't forget to include the colon at the end of the method name, it is an integral part of the name. To complete the functionality of the "Edit" button, all that is left is to ensure that it is only ever enabled when an item is selected in the Table View. We achieve this by binding the "Enabled" property to the Words Controller, specifying "selection" for the "Controller Key" property and choosing "NSIsNotNil" for the "Value Transformer" property; this means that the Value Transformer will return true if the selection is not nil, thus enabling the button, or disabling it when the selection is nil. Once these bindings are in place, you should be able to save everything, compile the application and run it.

page 36

Bonus Draw(er)
Our application now provides the following features : 1. 2. 3. 4. A read-only browser form for Word objects An editable browser form for Language Objects A modal dialog for adding new Word objects A non-modal form for editing existing Word objects

However, the Cocoa frameworks provides us with yet another way of editing our Word objects, without opening a separate dialog or form; well, sort of The NSDrawer is a visual component that, when added to a XIB le, allows a "drawer" to slide out from under a form.

Adding such a drawer to a form can be done with absolutely no code and only takes a few minutes in Interface Builder.

page 37

Open the MainMenu.xib le in Interface Builder and nd the Windows section of the Library window; you should nd an item marked Window and Drawer. Dragging this object from the Library to the XIB document window will actually create three objects: a Drawer, a View and a Window. Due to a problem in Interface Builder, it isn't possible to add only an NSDrawer component, and its associated Content View, to a XIB le - the Inspector doesn't correctly display some of the properties from the Drawer. It is, therefore, easier to drop the combined Window and Drawer item onto the XIB document window and delete the unwanted Window object. The NSView object, entitled "Drawer Contents" is what we will be using to layout the content of the the drawer. Having dragged the three components, that make up the "Window and Drawer", onto the document window and deleted the extra Window object, we now need to connect the Drawer object to our own main form by Ctrl-dragging a connection from the Drawer object to our Window object, selecting the "parentWindow" outlet.

Activating the Drawer


Many Cocoa dialogs, like File Save, contain hidden sections, which can be accessed by use of a small, square, blue button containing a black, downwards-pointing triangle. This is known as a Disclosure Button and clicking on it will expand the dialog and change the black triangle to point upwards, ready to be used to collapse the dialog again.

For this example, we need a disclosure button but with the triangle pointing left or right. Unfortunately, either the Cocoa frameworks doesn't allow changing the direction of the triangle in a genuine Disclosure Button, or I just haven't found out how to do it; but I have managed to nd a workaround. Drag a Bevel Button from the Buttons section of the Library window, place and size it similarly to the above screenshot and select the Attributes pane of the Inspector. To obtain the desired appearance, the important properties are : 1. 2. 3. 4. Set the Bezel property to "Bevel" Set the Type to "Toggle" Set the Image to NSRightFacingTriangleTemplate Set the Alt. Image to NSLeftFacingTriangleTemplate

All that is required to get the button to activate the Drawer object is to Ctrl-drag a connection from the button to the Drawer object and select the "toggle:" outlet. If you look on Interface Builder's File menu, you will nd an item "Simulate Interface". This creates an instance of the form and, although no data is shown, allows for the testing of interactions between any connected controls. You should now be able to click on the "disclosure" button - the empty drawer should appear from the righthand edge of the form and the triangle on the button should point left. Clicking again on the button should close the drawer and reset the triangle to point, once again, to the right.

page 38

Designing the Drawer Content


Now that we have provided a button to show and hide the drawer, we need to setup the Content View to do something useful; in this case, to provide a means of editing the currently selected Word from the Table View. Because we want to allow the user to choose the Language for the selected Word object, we will need to add an NSArrayController to the XIB document window, just as we previously did for the other editor forms.

Moving to the Content View object, layout a couple of Labels, a Text Field and a Pop Up Button from the Inputs & Values section of the Library Select the Text Field and expand the Value binding in the Bindings pane of the Inspector. Set the Controller Key to "selection" and the Model Key Path to "text". Select the Pop Up Button and setup its bindings just as we did for the other editor forms. Use the Size tab of the Inspector to determine the width of this Drawer Content form and input this value for the Width, Min Width and Max Width properties of the Drawer object. You can experiment with the other properties on the Size tab of the Drawer object to see how they affect the vertical height and positioning of the Drawer. You should now be able to open the drawer and edit whichever Word object is currently selected in the Table View.

Conclusion
In the course of this tutorial we have used a variety of Cocoa technologies : 1. Core Data provides us with an Object Model and relieves us of the responsibility of having to design and maintain a database by managing persistence of instances of our business classes. 2. Managed Object Contexts are separate "views" on the stored objects; editing objects in one context doesn't affect the state of the same objects in another context, until the objects are saved and the two contexts reconciled. 3. Objects in a XIB le are instantiated when the XIB le is loaded. 4. Mac development is strongly based on the MVC (Model View Controller) design pattern. 5. We discovered that Actions are the equivalent of event handlers and that Outlets are specially marked elds. Both Actions and Outlets allow Interface Builder to make connections to other objects in a XIB le. 6. Cocoa Bindings allow us to bind a UI control to the property of a business object so that, when the property of the object changes value, the control is notied; and, when the value in a control is altered, that value is propagated to the property of the object it represents visually. 7. Action Invocation allows us to bind a button to a method that accepts parameters, which can be supplied from the Controller that mediates between the button end the method. 8. Data from the Core Data storage is connected to UI controls via Array and Object Controllers, which not only provide the data but can also react to commands, like Add and Remove, sent from buttons on a form. The internal state of the Controller can also affect the appearance of buttons connected to it. 9. Dialogs that are modal to a form instead of the whole application After following this tutorial, you should be able to create a simple application whose main form allows a user to browse a read-only list of Word objects, with a slide-out drawer for editing the selected Word. The application also includes a separate form for browsing an editable list of Language objects, a modal form for the creation of new Word objects and a non-modal form for editing existing Words.

page 39

You might also like