Learning Cocoa with Objective-C/Appendixes/Exercise Solutions

From WikiContent

< Learning Cocoa with Objective-C | Appendixes
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

This appendix gives some tips, hints, and answers to the exercises found at the end of each chapter.

Contents

Chapter 2

  1. Open a Finder window, and navigate to the /Developer/Applicationsfolder. Drag the Project Builder and Interface Builder icons to your Dock.
  2. Open a Finder window and navigate to the /Developer/Documentation/Cocoafolder. Drag the CocoaTopics.html file to your Dock.

Chapter 3

  1. One way to access the documentation:

    Open the /Developer/Documentation/Cocoa/CocoaTopics.html file in your web browser of choice, click on the Foundation link in the Objective-C Framework Reference section, and then follow the NSObject and NSString links.

  2. One way to find the documentation:

    In the Foundation reference document that the NSObject and NSString links are on, go to the bottom of the page, and click on the Functions link. You'll find the NSLog documentation on this page.

    Tip

    Use the Find functionality of your browser (Edit → Find, or [[Image:Learning Cocoa with Objective-C_I__tt614.png|]]-F on most browsers) to search for the NSLog documentation on this rather large page.

  3. One way to do it is to add the following line to the initWithName:artist: method of the Song class:
    NSLog(@"isa: %@ self %@", isa, self);
    

    This will print the name of the class as well as the description.


Chapter 4

  1. Here's one way to do it:

    Open the strings project we created, and place a breakpoint before the [artist release] statement. Debug the application, and enter in the following commands into the gdb console:

    (gdb) print-object [artist lowercaseString]
    (gdb) print-object [artist uppercaseString]
                      
    
  2. One possible solution is to create a project named "fileprinter" with the following main.m file:
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        if (argc > 1) {
                                 NSString * filename = [NSString stringWithCString:argv[1]];
                                 NSString * file = [NSString stringWithContentsOfFile:filename];
                                 printf("%s\n", [file UTF8String]);
                             }
        [pool release];
        return 0;
    }
    

    When executed from Project Builder, nothing will happen, but if you go to the terminal and issue the following commands, you will see the contents of the main.m file printed:

    [titanium:~] duncan% cd ~/LearningCocoa/fileprinter
    [titanium:~/LearningCocoa/fileprinter] duncan% build/fileprinter main.m
                      
    

    Try this with a few other files.

  3. One way to look up this documentation:

    Open the /Developer/Documentation/Cocoa/CocoaTopics.htmlfile in your web browser of choice, click on the Foundation link in the Objective-C Framework Reference section, and then follow the NSArray, NSSet and NSDictionary links.

  4. One solution is to add the following line of code to the main method:
                         [array writeToFile:@"foo.plist" atomically:YES];
                      
    

    When you run the arrays program with this additional line, a foo.plist file will be written into ~/LearningCocoa/arrays/build containing the elements of the array.

  5. One possible solution is to create a project named "dictionarysaver" with the following main.m file:
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        NSMutableDictionary * dict = [NSMutableDictionary dictionary];
                             [dict setObject:@"The Meaning of Life" forKey:@"A String"];
                             [dict setObject:[NSNumber numberWithInt:42] forKey:@"An Integer"];
                             [dict writeToFile:@"dict.plist" atomically:YES];
        [pool release];
        return 0;
    }
    

    When you run this program, the dictionary will be written to the dict.plist file in the ~/LearningCocoa/dictionarysaverfolder. This file will look as follows:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.
    com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
            <key>A String</key>
            <string>The Meaning of Life</string>
            <key>An Integer</key>
            <integer>42</integer>
    </dict>
    </plist>
    
  6. The dictionaries example application doesn't release the dict object. The following line of code is needed after the printf statement:
                         [dict release];
                      
    

Chapter 5

  1. One way to do this is to select the text labels, then change the font using the Font Panel (Format → Show Fonts, or [[Image:Learning Cocoa with Objective-C_I__tt623.png|]]-T).
  2. One way to do this is to select the totalField, then change the font color using the Font Panel. To access the color picker, use the Extras... pull-down menu on the Font Panel.

Chapter 6

  1. The documentation can be found in the /Developer/Documentation/Cocoa /CocoaTopics.htmlfolder. Click on the Application Kit link in the Objective-C Framework Reference section, and then follow the NSWindow and NSView links.
  2. The easiest way to do this is to select the window in Interface Builder and change the title using the Attributes inspector. You can bring the inspector up by hitting Shift-[[Image:Learning Cocoa with Objective-C_I__tt624.png|]]-I.
  3. One way to do this is to edit the Controller.h file directly to match the following code:
    @interface Controller : NSObject
    {
        IBOutlet id converter;
        IBOutlet NSTextField dollarField;
                             IBOutlet NSTextField rateField;
                             IBOutlet NSTextField totalField;
    }
    - (IBAction)convert:(id)sender;
    @end
    

Chapter 7

  1. One way to do this is to edit the drawRect: method as follows:
    - (void)drawRect:(NSRect)rect
    {
        [[NSColor colorWithCalibratedRed:1.0 green:0.0 blue:0.0 alpha:1.0] set];
        NSRectFill([self bounds]);
    }
    
  2. The easiest way to do this is to add the drawing code from the String View application into the Line View drawRect: method as follows:
    - (void)drawRect:(NSRect)rect
    {
        NSRect bounds = [self bounds];
        NSPoint bottom = NSMakePoint((bounds.size.width/2.0), 0);
        NSPoint top = NSMakePoint((bounds.size.width/2.0), bounds.size.height);
        NSPoint left = NSMakePoint(0, (bounds.size.height/2.0));
        NSPoint right = NSMakePoint(bounds.size.width, (bounds.size.height/2.0));
    
        [[NSColor whiteColor] set];
        [NSBezierPath fillRect:bounds];
    
        [[NSColor blackColor] set];
        [NSBezierPath strokeRect:bounds];
    
        [NSBezierPath strokeLineFromPoint:top toPoint:bottom];
        [NSBezierPath strokeLineFromPoint:right toPoint:left];
    
        [[NSBezierPath bezierPathWithOvalInRect:bounds] stroke];
        NSString * hello = @"Hello World!";
                             NSMutableDictionary * attribs = [NSMutableDictionary dictionary];
                             [attribs setObject:[NSFont fontWithName:@"Times" size:24]
                                         forKey:NSFontAttributeName];
                             [attribs setObject:[NSColor redColor]               
                                         forKey:NSForegroundColorAttributeName];
                             [hello drawAtPoint:NSMakePoint((bounds.size.width/2.0),
                                                            (bounds.size.height/2.0))
                                 withAttributes:attribs];
      
    }
    @end
    
  3. One way to do this is to insert the following line of code before drawing any of the lines in Line View:
                         [NSBezierPath setDefaultLineWidth:3.0];
                      
    

    This will cause all paths to be drawn with 3-point-wide strokes. Try some other values.

  4. One way of finding the NSBezierPath documentation is to click the Find tab in Project Builder (or use the menu Find → Show Batch Find). Enter NSBezierPath into the Find box, and hit Return. After a few moments, Project Builder will show you all the occurrences of the NSBezierPath string, both in your project and in the frameworks against which it's linked. To limit the search to just your project, adjust the "This Project" pull-down to "This Project, no frameworks."

Chapter 8

  1. The easiest way to accomplish this is to rename the mouseDown: method to mouseDragged:.
  2. One way to do this is to modify the initWithFrame: method as follows:
    - (id)initWithFrame:(NSRect)frame
    {
        self = [super initWithFrame:frame];
        center.x = frame.size.width / 2.0;
                             center.y = frame.size.height / 2.0;
        radius = 20.0;
        color = [[NSColor redColor] retain];
        return self;
    }
    
  3. One way of changing the setRadius: method is as follows:
                         - (IBAction)setRadius:(id)sender
                         {
                             float newRadius = [sender floatValue];
                             if (newRadius != radius) {
                                 radius = newRadius;
                                 [self setNeedsDisplay:YES];
                             }
                         }
                      
    

    This will ensure that a redraw of the interface only occurs when it is needed.

  4. One way to do this is to make the MyDelegate object instance a delegate of NSApplication and implement the applicationShouldTerminate: method. To do this, Control-drag a connection from File's Owner to MyDelegate in Interface Builder, then add the following method to the MyDelegate class:
    - (NSTerminationReply)applicationShouldTerminate:(NSApplication *)sender
    {
        int answer = NSRunAlertPanel(@"Quit", @"Are you sure?",
                                    @"Quit", @"Cancel", nil);
        if (answer == NSAlertDefaultReturn) {
            return NSTerminateNow;
        } else {
            return NSTerminateCancel;
        }
    }
    
  5. We suggest changing it to blue by modifying the initWithFrame: method as follows:
    - (id)initWithFrame:(NSRect)frame
    {
        self = [super initWithFrame:frame];
        center.x = frame.size.width / 2;
        center.y = frame.size.height / 2;
        radius = 20.0;
        color = [[NSColor blueColor] retain];
        return self;
    }
    

    Of course you can change it to whatever color you please.

  6. To do this requires setting the springs appropriately in Interface Builder.
    1. Set the springs for the Dot View as shown in Figure A-1.

      Figure A-1. Spring settings for Dot View

      Spring settings for Dot View

    2. Set the springs for the slider as shown in Figure A-2.

      Figure A-2. Spring settings for Dot View's slider

      Spring settings for Dot View's slider

    3. Set the springs for the color well as shown in Figure A-3.

      Figure A-3. Spring settings for Dot View's color well

      Spring settings for Dot View's color well



Chapter 9

  1. Go into Interface Builder, and either edit the column header, either directly or using the inspector.
  2. One way to do it is to make the MyDataSource object instance a delegate of NSApplication and implement the applicationShouldTerminate: method. To do this, Control-drag a connection from File's Owner to MyDataSource in Interface Builder, then add the following method to the MyDataSource class:
    - (NSTerminationReply)applicationShouldTerminate:(NSApplication *)sender
    {
        int answer = NSRunAlertPanel(@"Quit", @"Are you sure?",
                                    @"Quit", @"Cancel", nil);
        if (answer == NSAlertDefaultReturn) {
            return NSTerminateNow;
        } else {
            return NSTerminateCancel;
        }
    }
    
  3. The FoodItem class needs a dealloc method to release the name and price variables.

Chapter 10

  1. Once again, the documentation can be found in the /Developer/Documentation /Cocoa/CocoaTopics.htmlfile.
  2. In the active target panel of Project Builder (Project → Edit Active Target), add a document type with the following settings:
    • Name: Property List
    • Extensions: plist
    • Document Class: MyDocument

    Build and run ([[Image:Learning Cocoa with Objective-C_I__tt637.png|]]-R) the application. You should be able to read and write plist files now.

  3. The revert functionality doesn't work because the windowControllerDidLoadNib method isn't called, and that is where we turn the contents of a file into a string to use. To fix this, edit the loadDataRepresentation:ofType: method as follows:
    - (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)type
    {
        if (textView){
                                 NSString * text = [[NSString alloc]initWithData:dataFromFile
                                                                        encoding:NSUTF8StringEncoding];
                                 [textView setString:text];
                                 [text release];
                             } else {
                                 dataFromFile = [data retain];
                             }
                             return YES;
    }
    

Chapter 11

  1. To turn on the ability for the TextView to handle graphics from within Interface Builder, simply click on the TextView, and bring up the Attributes inspector, as shown in Figure A-4. Simply click on the radio buttons, and then remove the corresponding lines of code from MyDocument.m.

    Figure A-4. TextView attribute inspector

    TextView attribute inspector

  2. Add the following line to the awakeFromNib: method:
                         [textView setRulerVisible:YES];
                      
    
  3. One solution:

    Add a menu item named "Speak" to the File menu of the MainMenu.nib file. Connect the menu item to FirstResponder.startSpeaking by Control-dragging a connection from the menu item to the First Responder icon. This makes our speakText method obsolete, so it can be removed.

  4. One way to do this is to modify the analyzeText: method, as shown here:
    - (IBAction)analyzeText:(id)sender
    {
        int count = 0;                                                       
        int fontChanges = -1;                                                
        id lastAttribute = nil;                                              
        NSTextStorage * storage = [textView textStorage];                    
        
        while (count < [storage length]) {                                   
            id attributeValue = [storage attribute:NSFontAttributeName
                                        atIndex:count
                                 effectiveRange:nil];
            if (attributeValue != lastAttribute) {                           
                fontChanges++;
            }
            lastAttribute = attributeValue;                                  
            count++;                                                         
        }
         
        NSBeginAlertSheet(@"Analysis",         // title                       // i                              
                          @"OK",               // default button label
                          nil,                 // cancel button label
                          nil,                 // other button label
                          [textView window],   // document window
                          nil,                 // modal delegate
                          NULL,                // selector to method
                          NULL,                // dismiss selector
                          nil,                 // context info
                          @"Font Changes %i\nCharacter Count %i",
                                               fontChanges, [storage length]);
    }
    

Chapter 12

  1. The easiest way to do this is to add the Font menu to the MainMenu.nib file. Then, in your application, use the Font menu to bring up the Font Panel (or hit [[Image:Learning Cocoa with Objective-C_I__tt642.png|]]-T).
  2. This is almost a trick question.

    By default, the File → Print command works and will print out the Dot View window. To print just the Dot View, disconnect the File → Print menu item from FirstResponder.print, then connect it (by Control-dragging) to the DotView area and the print: action.

  3. Simply drag the image view to encompass the entire window. You may even want to turn the border off so that only the image contained by the view will print. Try setting the image view to some of the images in the /Library/Desktop Pictures folder for printing.

Chapter 13

  1. One way to do this:

    Add the following method to Controller.m, and connect it to a "Previous Image" button:

                         - (IBAction)prevousImage:(id)sender
                         {
                             if (currentImage == 0) {
                                 currentImage = [images count] - 1;
                             } else {
                                 currentImage--;
                             }
                             [imageView setImage objectAtIndex:currentImage];
                         }
                      
    
  2. One way to do this is in Interface Builder. In the inspector, set the key equivalent of the next button to "n" and of the previous button to "p".

Warning

Be sure to hit Return after entering in the key equivalent so that it is stored in the nib file. We had a few problems with this feature on some builds of Interface Builder until we discovered this.

Chapter 14

  1. The best way to do this is to follow the procedure that we detailed in the chapter:
    1. Create a localized nib file variant.
    2. Modify the various strings in the UI for their new language.

      If you don't know another language, or if you just want to check your translations, use the Translation channel of Sherlock 3.



Chapter 15

  1. Find the ~/Library/Preferences/com.oreilly.Favorites file, and double-click it to open it in the Property List Editor (/Developer/Applications).
  2. Create a button on the interface for the Favorites application, and have it call the following action method:
                         - (IBAction)reset:(id)sender
                         {
                             [prefs removeObjectForKey:@"FavBook"];
                             [prefs removeObjectForKey:@"FavCity"];
                             [prefs removeObjectForKey:@"FavColor"];
                             [prefs removeObjectForKey:@"FavFood"];
                         }
                      
    
  3. One way to do this:
    1. Create a default.plist file using Property List Editor. The root key should be of type Dictionary, and it should have four children. The file should look as follows when you are done with it:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
      <plist version="0.9">
      <dict>
          <key>FavBook</key>
          <string>Learning Cocoa</string>
          <key>FavCity</key>
          <string>San Francisco </string>
          <key>FavColor</key>
          <string>Red</string>
          <key>FavFood</key>
          <string>Mexican</string>
      </dict>
      </plist>
      
    2. Add the default.plist file as a resource to your project.
    3. Change the init method as follows:
      - (id)init
      {
          [super init];
          NSString file = [[NSBundle mainBundle]
                                         pathForResource:@"default" ofType:@"plist];
                                     NSDictionary * defaultPrefs = [NSDictionary 
                                         dictionaryWithContentsOfFile:file];
                                     prefs = [[NSUserDefaults standardUserDefaults] retain];
                                     [prefs registerDefaults:defaultPrefs];
          return self;
      }
      

  4. One way to do this:

    Create a new action method in the Controller.h file as follows:

                         - (IBAction)rateUpdated:(id)sender
                         {
                             [[NSUserDefaults standardUserDefaults] setFloatValue:[sender floatValue]
                                                                          forKey:@"rate"];
                         }
                      
    

    Hook the rateField text field to the rateUpdated: action method so that it is called whenever the contents of the field are changed. Next, add an awakeFromNib method to the Controller.h file as follows:

                         - (void)awakeFromNib
                         {
                             [rateField setFloatValue:[[NSUserDefaults standardUserDefaults]
                                 floatForKey:@"rate"];
                         }
                      
    

Chapter 16

  1. One way to do this:
    1. Add an outlet named wordCountField to the Controller class.
    2. Add two text labels to the utility panel: the first named "Number of Words" and the second being a place holder. Connect the place holder to the wordCountField outlet in the File's Owner object proxy.
    3. Add the following code to the showInfoPanel: method:
      #import "Controller.h"
      
      @implementation Controller
      
      - (IBAction)showInfoPanel:(id)sender
      {
          if (!infoPanelController) {                                 
              [NSBundle loadNibNamed:@"InfoPanel" owner:self];            
              infoPanelController = [[NSWindowController alloc]           
                  initWithWindow:infoPanel];
          }
          [textLengthField setIntValue:[[textView textStorage] length]];
          [wordCountField setIntValue:[[textStorage 
                                         componentsSeparatedByString:@" "] count];  
          [infoPanelController showWindow:self];                          
      }
      @end
      

  2. Follow the same process as listed earlier with a new paraCountField, and separate the string based on the \n character.
  3. Use the following process to guide you:
    1. Add a radiusTextField outlet and a showInfoPanel: action method to the DotView class.
    2. Create a new nib file named InfoPanel.nib.
    3. Lay out a user interface that allows the radius to be displayed via a radiusTextField outlet.
    4. Set the File's Owner of InfoPanel.nib to the DotView class.
    5. Add a menu item to the menu bar to show the Info panel, and connect it to the showInfoPanel: action method.

Chapter 17

We're going to leave the final exercises up to you to complete on your own. By now you should be able to tackle them without any help from us. However, here are a couple of ideas for an application to write:

  • A home inventory keeper that can keep a list of all the items in your house, complete with serial numbers and date of purchase
  • An application that is like Dot View, but can draw shapes with a variable number of sides

Good luck!

Personal tools