Learning Cocoa with Objective-C/Miscellaneous Topics/Accessory Windows

From WikiContent

< Learning Cocoa with Objective-C | Miscellaneous Topics
Revision as of 12:56, 7 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Learning Cocoa with Objective-C

When a Cocoa-based application starts up, it loads all of the objects in the main nib file, then initializes, connects, and displays them. This can take some time, and the more objects you have in your interface, the longer it will take. While this process is happening, nothing else can happen in your application, and the user gets to watch the application icon bounce.

To shorten load time, lower initial memory consumption, and help organize your application better, Cocoa lets you use multiple nib files and load them on demand. For example, you can have separate nib files for info panels, toolbars, and dialog boxes. This chapter shows you how to use auxiliary windows with your application, explaining how to load them and how to manipulate their contents to create inspectors.

Contents

The Role of File's Owner

So far, we haven't paid much attention to the special File's Owner object that shows up in Interface Builder—other than to say that it is a proxy object that "owns" the objects in a nib file. However, when working with multiple nib files, it is crucial to understand the role that the File's Owner plays as the object that loads—and thus "owns"—the nib file. When working with a single nib file, it is easy enough to create connections between your code and controls in the nib file. However, when working with multiple nib files, it becomes more difficult to make clear connections between controls in an auxiliary nib file and those of the main application. To make these connections, you use the File's Owner object proxy.

In the applications that we've put together so far, File's Owner has been assigned to the main object (an instance of NSApplication) of the application or, in the case of a document-based application, a document object (a subclass of NSDocument). You can see this connection in Interface Builder by selecting the File's Owner and looking at the Attribute's inspector, as shown in Figure 16-1.

Figure 16-1. The default File's Owner object

The default File's Owner object

As you can see from Figure 16-1, you can assign the File's Owner object to any class. When creating an auxiliary nib file, you will need to assign the File's Owner proxy to the class that will load the nib and be responsible for mediating between the functionality of the secondary window and the rest of the application.

Making an Info Window

To illustrate how to use auxiliary windows and how the File's Owner proxy enables the various parts of an application to communicate, we will create a simple inspector to tell us how many characters are in a text view.

Create the Main Interface

  1. Create a new Cocoa Application project in Project Builder (File → New Project → Application → Cocoa Application) named "Simple Inspector", and save it in your ~/LearningCocoafolder.
  2. Open the MainMenu.nib file in Interface Builder.
  3. Create a subclass of NSObject in Interface Builder, named Controller. (Click on the Classes tab of the MainMenu.nib window, find NSObject, Control-click it, and select Subclass NSObject).
  4. Create an action method on the Controller class, named showInfoPanel:.
  5. Create four outlets on the Controller class, named infoPanel, infoPanelController, textLengthField, and textView, and assign their types as shown in Table 16-1 and Figure 16-2.

    Table 16-1. Assigning the types for the outlets of the Simple Inspector application

    Outlet Type
    infoPanel
    
    NSPanel
    
    infoPanelController
    
    NSWindowController
    
    textLengthField
    
    NSTextField
    
    textView
    
    NSTextView
    


    Figure 16-2. Creating outlets on the Controller class

    Creating outlets on the Controller class

    The infoPanel outlet will hold a reference to our inspector panel. The infoPanelController will serve as the window controller for the panel. The textFieldLength outlet will point to a text field in our inspector panel that will display the total number of characters in the textView.

  6. Generate the source-code files for the Controller class (Classes → Create Files for Controller).
  7. Instantiate the Controller class (Classes → Instantiate Controller).
  8. Drag a text view out to the application's main window, and set its Autosizing attributes so that it will resize along with the window that contains it.
  9. Control-drag a connection from the Controller instance object to the text view, as shown in Figure 16-3, and connect it to the textView outlet.

    Figure 16-3. Connecting the text view to the Controller

    Connecting the text view to the Controller

  10. Add a Show Info menu item to the MainMenu's Window menu, as shown in Figure 16-4. Give it a key equivalent of [[Image:Learning Cocoa with Objective-C_I_5_tt570.png|]]-I. This will let the user of our application get the info panel either by using the menu item or by just using the Command-key equivalent.

    From the Cocoa-Menus palette:

    1. Drag an Item to the menu, and place it as shown in Figure 16-4.
    2. Use the Info panel to change its name and assign a Command-key equivalent.

    Figure 16-4. Adding a Show Info window to the application's menu bar

    Adding a Show Info window to the application's menu bar

  11. Connect the Show Info menu item to the Controller's showInfoPanel: action method by Control-dragging a connection from the menu item to the Controller object, as shown in Figure 16-5.

    Figure 16-5. Connecting the menu item to the showInfoPanel: action method

    Connecting the menu item to the showInfoPanel: action method

  12. Save ([[Image:Learning Cocoa with Objective-C_I_5_tt573.png|]]-S) and close ([[Image:Learning Cocoa with Objective-C_I_5_tt574.png|]]-W) the nib file.

Create the Inspector Panel

Now that we've created the controller object for the application, the application's main window with a text view, and the menu item for the user to request the info panel, we need to create a new nib file to contain the user interface for the info panel.

  1. Create a new nib file in Interface Builder (File → New). Interface builder will show a dialog box similar to that in Figure 16-6. Select Empty and click New.

    Figure 16-6. Interface Builder's starting point

    Interface Builder's starting point

  2. Save the file as InfoPanel.nib in your ~/LearningCocoa/Simple Inspector /English.lprojfolder, as shown in Figure 16-7. You will be prompted to add the nib file to the Simple Inspector target; click the Add button to do so.

    Figure 16-7. Saving the InfoPanel.nib file

    Saving the InfoPanel.nib file

  3. Now, we need to designate our Controller class as the object that will be the File's Owner for this nib file at runtime. To let Interface Builder know that the Controller class exists, drag the Controller.hfile from Project Builder onto the InfoPanel.nib window.
  4. Select the File's Owner proxy object in the InfoPanel.nib window, and, using the File's Owner Info inspector as shown in Figure 16-8, set the File's Owner class to Controller.

    Figure 16-8. Setting the File's Owner class

    Setting the File's Owner class

    This tells Interface Builder that the class responsible for loading this nib file will be of type Controller. By setting this class as the File's Owner, we can designate that various interface components in this nib file should be connected to the outlets of the Controller class.

  5. Drag out a panel from the Windows palette. Name the panel Info by using the NSPanel Info inspector, as shown in Figure 16-9. Also, make sure that the Hide on deactivate and Utility window (Panel only) options are checked. These options will make our panel look like an Info panel and ensure that it disappears when our application is not active.

    Figure 16-9. Creating the Info panel

    Creating the Info panel

  6. Drag two text fields onto the panel; name them "Length of Text View:" and "Number". Control-drag a connection from the File's Owner proxy object (remember, this is a stand-in for our Controller class) to the Number field, and set the textLengthField outlet, as shown in Figure 16-10.

    Figure 16-10. Connecting the textLengthField

    Connecting the textLengthField

  7. Control-drag a connection from the File's Owner object to the Panel object in the InfoPanel.nib window, as shown in Figure 16-11, and connect it to the infoPanel outlet.

    Figure 16-11. Connecting the infoPanel outlet

    Connecting the infoPanel outlet

  8. Save the nib file ([[Image:Learning Cocoa with Objective-C_I_5_tt581.png|]]-S), and return to Project Builder.

Implement the Code

Now that we have our two nib files designed, it's time to implement the code that ties all the pieces together.

  1. Edit the Controller.m file as follows:
    #import "Controller.h"
    
    @implementation Controller
    
    - (IBAction)showInfoPanel:(id)sender
    {
        if (!infoPanelController) {                                        // a
                                    [NSBundle loadNibNamed:@"InfoPanel" owner:self];               // b
                                    infoPanelController = [[NSWindowController alloc]              // c
                                        initWithWindow:infoPanel];
                                }
                                [textLengthField setIntValue:[[textView textStorage] length]];     // d
                                [infoPanelController showWindow:self];                             // e
    }
    
    @end
    

    The code we added performs the following tasks:

    1. Checks to see if we have a reference to a window controller for the Info panel. If we don't, it means that we need to load the nib file that contains the panel. On the other hand, if we have a reference, then we don't need to load it.
    2. Loads the InfoPanel nib file. Notice that we don't use the .nib extension here. When the nib is loaded, the connections assigned to the File's Owner will be made to the Controller object.
    3. Creates a new window controller, assigns it to the infoPanelController variable, and initializes it to use the panel loaded from the nib.
    4. Sets the textLengthField to the length of the textStorage object.
    5. Shows the Info panel.
  2. Build and run ([[Image:Learning Cocoa with Objective-C_I_5_tt583.png|]]-R) the application. Enter some text into the text view, then show the Info panel (Window → Show Info or [[Image:Learning Cocoa with Objective-C_I_5_tt584.png|]]-I). You should see something like Figure 16-12.

    Figure 16-12. Our inspector in action

    Our inspector in action


Obviously, we could add all sorts of information to our inspector window, such as the number of words in the file, number of paragraphs, etc. We'll leave these tasks as exercises at the end of the chapter.

Tracking Changes with Notifications

Notice that we have a problem with our application. When you show the Info panel, you see the number of characters in the text view, but when you change the text in the text view, the info panel becomes out of date. The information only updates when you close the Info panel and select Show Info again. We see this behavior because we are only setting the information in the panel when the user tells the application to show the panel. We probably want this information to be updated dynamically as the text view's contents change.

To get this functionality, we'll use a notification that the text-view object will post to the notification center whenever its contents change. For more information about notifications, see Chapter 8.

  1. Add the following code to the Controller.m file:
    #import "Controller.h"
    
    @implementation Controller
    - (void)awakeFromNib                                                      // a
                            {
                                NSNotificationCenter * center = [NSNotificationCenter defaultCenter];  
                                [center addObserver:self                                               
                                           selector:@selector(textDidChange:)
                                               name:NSTextDidChangeNotification
                                             object:textView];
                            }
    
                            - (void)textDidChange:(NSNotification *)notification                   // b
                            {
                                [textLengthField setIntValue:[[textView textStorage] length]];         
                            }
    
    - (IBAction)showInfoPanel:(id)sender
    {
        if (!infoPanelController) {
            [NSBundle loadNibNamed:@"InfoPanel" owner:self];
            infoPanelController = [[NSWindowController alloc] 
                initWithWindow:infoPanel];
        }
        [textLengthField setIntValue:[[textView textStorage] length]];
        [infoPanelController showWindow:self];
    }
    
    @end
    

    The code we added performs the following tasks:

    1. Adds an awakeFromNib method that will add the Controller instance as an observer to the default notification center interested in NSTextDidChangeNotification events on the textView object.
    2. Implements the callback method that the notification center will call whenever text changes in the text view. We simply update the textLengthField in our inspector panel.
  2. Build and run ([[Image:Learning Cocoa with Objective-C_I_5_tt587.png|]]-R) the application. Show the Info panel (Window → Show Info or [[Image:Learning Cocoa with Objective-C_I_5_tt588.png|]]-I), then type text into the text view. The info panel will now keep up with the correct number of characters in the text view as you type.

Exercises

  1. Add a field to the Simple Inspector application that will display the number of words in a document.
  2. Add a field to the Simple Inspector application that will display the number of paragraphs in a document.
  3. Add an Info window to Dot View (see Chapter 8) that will let the user know the current diameter of the dot.
Personal tools