Chapter 11. Rich-Text Handling

In Chapter 10, we showed how Cocoa’s multiple-document architecture takes care of many of the tasks involved in building a document-centric application, and we used a simple text editor as an example. Cocoa’s text-handling ability goes much further by supporting multiple fonts, various paragraph styles, embedded images, undo, drag-and-drop, and even spell checking. It can handle differences in text directionality and provides sophisticated typesetting capabilities, such as the ability to control kerning between characters.

In this chapter, we are going to examine the functionality of NSTextView and the other classes that compose the text system. We’ll then dive into code and add the following functionality, one step at a time, to a rich-text editor:

  • Enable the font menu

  • Work with attributed text

  • Register undo actions

  • Enable the text menu

  • Handle embedded images

  • Add a special feature that we’ll save for last

That’s a lot of ground to cover, so let’s get going!

Cocoa’s Text System

Cocoa’s text system, which underlies the functionality we worked with in Chapter 10, consists of three API layers, as shown in Figure 11-1. You can see the same Model-View-Controller (MVC) paradigm we’ve talked about previously in the design of the text system. At the top layer is the NSTextView that provides the on-screen view of text.

Three layers of the Cocoa text system
Figure 11-1. Three layers of the Cocoa text system

At the bottom layer, the NSTextStorage class gives programmatic access to the text. This allows you to search for text and manipulate paragraph and character styles without incurring the overhead of laying out the text for display.

In the middle layer are the NSLayoutManager and NSTextContainer classes, which control the way text is laid out on-screen or on the printed page. An NSTextContainer object defines a region where text can be laid out. Typically, this is a rectangular region, but subclasses can support other shapes. If a container’s area is inset from the text view’s bounds, a margin appears around the text. An instance of the NSLayoutManager class displays the text contained in an NSTextStorage object, rendering text in an NSTextView’s display according to the areas defined by NSTextContainer objects.

For most uses, the API provided by the NSTextView class is all that you need to learn to enable rich-text functionality in your applications.

Supported Text Data Types

Cocoa’s text system supports the following types of data that can be read, displayed, and saved:

A simple string

As we saw in Chapter 10, you can set and read the contents of a text view to and from an NSString instance. This is the easiest way to deal with plain text files.

Data as rich text

The Rich Text Format (RTF) is a standard created by Microsoft for representing text with multiple fonts and colors. RTF is supported by many word processors (including the TextEdit application that comes with Mac OS X) as an interchange format, but serves quite well as a primary document format. Files saved in RTF format will be assigned an .rtf extension.

Data as rich text with images

Standard RTF files can also contain attachments, such as images, audio clips, and even QuickTime movies, which are embedded in the file. These files, known as RTFD (the “D” stands for directory) use a type of package format, or directory, in which the embedded files of the RTF document are stored. The package will contain the RTF file (e.g., text.rtf), along with any associated attachments (e.g., fuzzball.tiff). When the file is saved, it will be assigned an .rtfd extension.

Working with File Wrappers

Because RTFD files are not simple files, but are composed of many files in a directory structure, the dataRepresentationOfType: and loadDataRepresentation:ofType: methods will not read them. To handle complex file types that consist of bundled directories, Cocoa provides file wrappers . File wrappers can be of three distinct types:

  • A directory wrapper that holds a directory and all the files and subdirectories within it

  • A regular file wrapper that holds the contents of a single file

  • A link wrapper that represents a symbolic link, or alias, in the filesystem

The NSDocument class provides the following methods for loading data from, and saving data to, file wrappers:

- (BOOL)loadFileWrapperRepresentation:(NSFileWrapper *)wrapper ofType:(NSString *)type

Loads the document data contained by the given file wrapper into the receiving NSDocument object

- (NSFileWrapper *)fileWrapperRepresentationOfType:(NSString *)type

Returns a file wrapper object that represents the contents of the document for a given document type

In fact, the NSDocument implementation of these methods actually calls the dataRepresentationOfType: and loadDataRepresentation:ofType: methods that we used in the Simple Text Edit application in Chapter 10. By overloading the file wrapper load and save methods, we can support RTFD.

Creating a Rich-Text Editor

To show how to work with rich text, we’re going to create a new project similar to the Simple Text Edit application. The big difference is that the application we’ll build in this chapter will work with rich-text files instead of just plain text. We’ll make a few changes to the recipe along the way; but not too much has changed, so we’ll zip through the parts that we’ve already covered. Refer back to Chapter 10 if you need help with any of the steps.

To get started:

  1. Launch Project Builder, and choose New Project from the File menu (File New Project).

  2. From the New Project window, select Application Cocoa Document-based Application.

  3. Name the project “RTF Edit”, and save it into your ~/LearningCocoa folder.

  4. Compose the UI by opening the MyDocument.nib file in Interface Builder and performing the following steps:

    1. Remove the default text field.

    2. Drag an NSTextView from the Views palette to the application’s window.

    3. Resize the text view so that it occupies the entire window.

    4. Change the Autosizing options so that the view will follow changes in the window’s size.

  5. In Project Builder, open MyDocument.h from the Classes directory, and add a declaration for the text view’s outlet.

    #import <Cocoa/Cocoa.h>
    
    @interface MyDocument : NSDocument
    {
        IBOutlet NSTextView * textView;
    }
    @end
  6. Save (File Save, or

    image with no caption

    -S) MyDocument.h, and then drag it onto Interface Builder’s MyDocument.nib window so that Interface Builder can pick up the change to the file.

  7. Control-drag a connection from the File’s Owner object (remember, this is a proxy for the MyDocument instance at runtime) to the NSTextView we added in step 4, and connect it to the textView outlet.

  8. Save (File Save, or

    image with no caption

    -S) the nib file.

  9. In Project Builder, open the active target (Project Edit Active Target, or Option-

    image with no caption

    -E), and select the Info.plist Entries Simple View Document Types item in the outline.

  10. Modify the default document type entry as shown in Figure 11-12. Simply click on the “DocumentType” entry to select it, rename “DocumentType” to “Rich Text”, enter the values (rtf for Extension and RTF for OS types) into the Document Type Information area, and click Change to apply these settings.

  11. Open MyDocument.h, and add the dataFromFile instance variable to hold the raw RTF data loaded from a file.

    #import <Cocoa/Cocoa.h>
    
    @interface MyDocument : NSDocument
    {
        IBOutlet NSTextView * textView;
        NSAttributedString * rtfData;
    }
    @end
    Setting the application settings for RTF Edit. Don’t forget to hit the Change button!
    Figure 11-2. Setting the application settings for RTF Edit. Don’t forget to hit the Change button!
  12. Open MyDocument.m, and remove the loadDataRepresentation:ofType: and dataRepresentationOfType: methods. We are removing them now because later on we will load RTFD data from a directory, and we will need the functionality that the file wrapper version of these methods will give us.

  13. Implement the loadFileWrapperRepresentation:ofType:.method:

                         - (BOOL)loadFileWrapperRepresentation:(NSFileWrapper *)wrapper 
                                                        ofType:(NSString *)type
                         {
                             rtfData = [[NSAttributedString alloc]
                                 initWithRTF:[wrapper regularFileContents] documentAttributes:nil];   // a
                             if (textView) {                                                          // b
                                 [[textView textStorage]
                                     replaceCharactersInRange:NSMakeRange(0, [[textView string] length])];
                                         withAttributedString:rtfData];
                                 [rtfData release]; 
                              }
                              return YES;
                         }

    The code we added does the following things:

    1. Creates a new NSAttributedString based on the RTF contents of the file wrapper.

    2. If there is a textView instead, we will load the RTF straight into it.

  14. Next, implement the fileWrapperRepresentationOfType: method so that the document can save its contents.

                         - (NSFileWrapper *)fileWrapperRepresentationOfType:(NSString *)type
                         {
                             NSRange range = NSMakeRange(0,[[textView string] length];
                             NSFileWrapper * wrapper = [[NSFileWrapper alloc]
                                 initRegularFileWithContents:[textView RTFFromRange:range]];
                             return [wrapper autorelease];
                          }
  15. Finally, implement the windowControllerDidLoadNib: method.

    - (void)windowControllerDidLoadNib:(NSWindowController *) aController
    {
        [super windowControllerDidLoadNib:aController];
        if (rtfData) {                                                           // a
                                 [[textView textStorage]
                                     replaceCharactersInRange:NSMakeRange(0, [[textView string] length]);
                                         withAttributedString:rtfData];
                                 [rtfData release];
                             }
                             [textView setAllowsUndo:YES];                                            // b
    }

    The code we added does the following things:

    1. If there is RTF data waiting, it is loaded into the text view.

    2. Sets the text view to allow for undo actions to be performed.

  16. Save the project (File Save, or

    image with no caption

    -S).

  17. Build and run (Build Build and Run, or

    image with no caption

    -R) the application. You should see the text editor as shown in Figure 11-3. You should be able to save and open rich-text files with it.

RTF Edit in action
Figure 11-3. RTF Edit in action

Enabling the Font Menu

Text views are already wired to work with the AppKit’s font system, which is defined by the NSFontPanel and NSFontManager classes. The font manager keeps track of the currently selected font, while the font panel lets users change the current font. When a user enters text into a text view, the view stores the text into the underlying text-storage object with attributes matching the current font.

The font manager, font panel, and an associated Font menu are set up using Interface Builder. To add font-handling functionality to our RTF Edit application, perform the following tasks:

  1. Open MainMenu.nib in Interface Builder.

  2. Open the Cocoa-Menus palette, and drag a Font menu to the MainMenu menu bar. Drop it between the Edit and Window menus, as shown in Figure 11-4.

    Adding the Font menu to RTF Edit
    Figure 11-4. Adding the Font menu to RTF Edit

    Notice that when you drop it, a Font Manager object is added to the nib. This is a reference to the Font Manager so that you can connect it to other objects in your application if needed.

  3. Save (File Save, or

    image with no caption

    -S) the nib file.

  4. Return to Project Builder, and Build and Run (

    image with no caption

    -R) the project. Try the following tasks:

    1. Type some text into the document, and select it. Open up the Font Panel (Font Show Fonts, or

      image with no caption

      -T), and change the font. Watch the selected text change, similar to what is shown in Figure 11-5.

      Using the Font Panel
      Figure 11-5. Using the Font Panel
    2. From the Extras pop-up menu at the bottom of the Font Panel, select the Color item. Change the text to a different color by selecting a color and clicking the Apply button.

    3. Close the Color and Font panels by clicking on their close window buttons.

    4. Save your file, and then open it in Text Edit to see your changes work in various applications.

Text Storage and Attributed Text

The text-storage object, an instance of NSTextStorage , serves as the data repository for the contents of a text view. Conceptually, each character of text in the text-storage object has an associated dictionary of keys and values that describe the characteristics, or attributes -- such as font, color, and paragraph style—of that character. Chapter 7’s String View application first introduced the notion of using attributes when drawing text. To make our text string show up in red, we set the following attribute in a dictionary (that we used when we drew the text):

[attribs setObject:[NSColor redColor]
            forKey:NSForegroundColorAttributeName];

You can associate any attribute you want with text; however, the attributes that Cocoa’s text system pays attention to are listed in Table 11-1.

Table 11-1. Standard Cocoa text attributes

Attribute identifier

Class of value

Default value

NSAttachmentAttributeName
NSTextAttachment

None

NSBackgroundColorAttributeName
NSColor

None

NSBaselineOffsetAttributeName
NSNumber (float)

0.0

NSFontAttributeName
NSFont

Helvetica, 12pt

NSForegroundColorAttributeName
NSColor

Black

NSKernAttributeName
NSNumber (float)

0.0

NSLigatureAttributeName
NSNumber (int)

1

NSLinkAttributeName
id

None

NSParagraphStyleName
NSParagraphStyle

Default paragraph style

NSSuperScriptAttributeName
NSNumber (int)

0

NSUnderlineStyleAttributeName
NSNumber (int)

None

In Table 11-1, we refer to NSNumber (int) and NSNumber (float). This means that the attribute should be set to an NSNumber object that was created with the type specified.

Working with Attributed Text

The text-storage class provides methods to access the various attributes of the text it contains. To show how to work with text attributes, we’ll add an analyzer to RTF Edit. Our analysis will count the number of characters in the document and give the number of font changes. To do so, follow these steps:

  1. In Project Builder, edit the MyDocument.h file, and add the following action:

    #import <Cocoa/Cocoa.h>
    
    @interface MyDocument : NSDocument
    {
        IBOutlet NSTextView * textView;
        NSAttributedString * rtfData;
    }
    - (IBAction)analyzeText:(id)sender;
    @end
  2. Save the MyDocument.h file, then open MyDocument.nib in Interface Builder.

  3. Reparse the MyDocument.h file in Interface Builder. To do this, drag the MyDocument.h file to the MyDocument.nib window.

  4. Add a button to our document interface, and name it Analyze, as shown in Figure 11-6.

    Adding an Analyze button to our interface
    Figure 11-6. Adding an Analyze button to our interface
  5. Control-drag a connection from the Analyze button to the File’s Owner object, and connect the button to the analyzeText: method.

  6. Save (

    image with no caption

    -S) the nib file, and return to Project Builder.

  7. Edit the MyDocument.m file, and add the analyzeText: method as shown.

                            - (IBAction)analyzeText:(id)sender
                            {
                                int count = 0;                                                       // a
                                int fontChanges = -1;                                                // b
                                id lastAttribute = nil;                                              // c
                                NSTextStorage * storage = [textView textStorage];                    // d
    
                                while (count < [storage length]) {                                   // e
                                    id attributeValue = [storage attribute:NSFontAttributeName
                                                                atIndex:count
                                                         effectiveRange:nil];
                                    if (attributeValue != lastAttribute) {                           // f
                                        fontChanges++;
                                    }
                                    lastAttribute = attributeValue;                                  // g
                                    count++;                                                         // h
                                }
    
                                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",
                                                  fontChanges);
                            }

    The code we added performs the following tasks:

    1. Sets up a counter to loop through all the characters in the document. This will allow us to examine the characters and notice font changes as we loop through the document.

    2. Sets up a counter that will be used to keep track of the number of font changes that are found in the document.

    3. Acts as a holder for the text-attribute object that was examined during a previous iteration of our loop.

    4. Gets a reference to the text-storage object behind the text view.

    5. Sets up a loop that will continue until we have examined every character in the document. Each time the loop is executed, the attribute value for the current character is obtained.

    6. Checks to see if the font attribute of the current character is the same as the last character. If not, we record the change in font.

    7. Stores this font attribute so that we can compare it to the font attribute we’ll see the next time through the loop.

    8. Increments our counter.

    9. Creates our message and displays it to the user on a sheet attached to the window. We can pass in nil and NULL to most of the arguments since the sheet is for informative purposes only.

  8. Save the project (File Save, or

    image with no caption

    -S).

  9. Build and run (

    image with no caption

    -R) the application. Type some text, change the fonts, and then hit the Analyze button. You should see something like Figure 11-7.

    RTF Edit analyzing its text
    Figure 11-7. RTF Edit analyzing its text

Our next set of additions to the code will change the formatting of the text in our document.

  1. In Project Builder, edit the MyDocument.h file, and add the following action:

    #import <Cocoa/Cocoa.h>
    
    @interface MyDocument : NSDocument
    {
        IBOutlet NSTextView * textView;
        NSData * dataFromFile;
    }
    - (IBAction)analyzeText:(id)sender;
    - (IBAction)clearFormatting:(id)sender;
    @end
  2. Save the MyDocument.h file; then open MyDocument.nib in Interface Builder.

  3. Reparse the MyDocument.h file in Interface Builder. To do this, drag the MyDocument.h file to the MyDocument.nib window.

  4. Add a button to our document interface, and name it Remove Formatting, as shown in Figure 11-8.

    Adding a Remove Formatting button to RTF Edit
    Figure 11-8. Adding a Remove Formatting button to RTF Edit
  5. Control-drag a connection from the Remove Formatting button to the File’s Owner object, and connect the button to the clearFormatting: method.

  6. Save (

    image with no caption

    -S) the nib file, and return to Project Builder.

  7. Edit the MyDocument.m file, and add the clearFormatting: method as shown.

                            - (IBAction)clearFormatting:(id)sender
                            {
                                NSTextStorage * storage = [textView textStorage];                     // a
                                NSRange range = NSMakeRange(0, [storage length]);                     // b
                                NSMutableDictionary * attribs = [NSMutableDictionary dictionary];     // c
                                [attribs setObject:[NSFont fontWithName:@"Helvetica" size:12]         // d
                                            forKey:NSFontAttributeName];
                                [storage setAttributes:attribs range:range];                          // e
                            }

    The code that we added performs the following tasks:

    1. Gets a reference to the text-storage object behind the text view

    2. Creates a range structure that will encompass all of the text in the storage object

    3. Creates a new mutable dictionary for the attributes to which we will set the text

    4. Adds an attribute to the dictionary to format the text with the Helvetica font in size 12

    5. Tells the storage to apply the new attributes to all characters

  8. Save the project (File Save, or

    image with no caption

    -S).

  9. Build and run (

    image with no caption

    -R) the application. Type some text, change the fonts, and then hit the Remove Formatting button. You should see all of your changes disappear.

Registering Undo Actions

There’s one small problem with our method to remove formatting. When you make changes after removing the formatting and then want to undo changes to a point in time before you cleared the formatting, things don’t work as expected. This is because we need to register the change with the undo manager .

The undo manager, implemented by the NSUndoManager class, is a general-purpose recorder of operations that can be undone or redone. An undoable operation is registered with the undo manager by specifying an object and a method to call on that object, along with an argument to pass to that method.

To allow the RTF Edit application to undo the removal of formatting, perform the following steps:

  1. Edit the removeFormatting: method in MyDocuments.m, adding the code indicated in boldface:

    - (IBAction)clearFormatting:(id)sender
    {
        NSTextStorage * storage = [textView textStorage];                     
        NSRange range = NSMakeRange(0, [storage length]);                     
        NSMutableDictionary * attribs = [NSMutableDictionary dictionary];  
        NSUndoManager * undoManager = [self undoManager];                     // a
                                [undoManager registerUndoWithTarget:storage                           // b
                                                           selector:@selector(setAttributedString:)
                                                             object:[storage copy]];
      
        [attribs setObject:[NSFont fontWithName:@"Helvetica"                  
                                           size:12]
                    forKey:NSFontAttributeName];
        [storage setAttributes:attribs range:range];
    }

    The code we added performs the following tasks:

    1. Gets the undoManager from the document. Each document has an associated undoManager.

    2. Registers an undo action with the undoManager. This action calls the underlying text-storage object’s setAttributedString: method with a copy of the current storage—effectively resetting the contents of the storage to the same state as before the change.

  2. Save the project (File Save,

    image with no caption

    -S).

  3. Build and run (

    image with no caption

    -R) the application. Undo should now work correctly.

Enabling the Text Menu

Not only do text views come wired to work with the AppKit’s font system, they are also prewired to work with paragraph-formatting rulers. These rulers let a user specify paragraph formatting for his documents. The easiest way to enable this functionality is to use Interface Builder. Perform the following steps:

  1. Open MainMenu.nib in Interface Builder.

  2. Open the Cocoa Menus palette, and drag a Text menu to the MainMenu menu bar. Drop it between the Font and Window menus, as shown in Figure 11-9.

    Adding the Text Menu to RTF Edit’s main menu bar
    Figure 11-9. Adding the Text Menu to RTF Edit’s main menu bar
  3. Save (File Save, or

    image with no caption

    -S) the nib file.

  4. Return to Project Builder, and Build and run (

    image with no caption

    -R) the project. Try the following:

    1. Select the Show Ruler menu item (Text Show Ruler). A ruler will appear on the text view, as shown in Figure 11-10. The ruler displays margin and tab markers similar to those used in full-blown word processors.

      RTF Edit sporting a ruler
      Figure 11-10. RTF Edit sporting a ruler
    2. Type some text, and change the paragraph alignment using the four buttons along the top-left hand side of the ruler.

    3. Create another paragraph, and change its indentation settings using the controls provided by the ruler.

Handling Embedded Images

The next piece of functionality we will add to RTF Edit is the ability to handle embedded images. To do this, we tell the text view that we want it place graphics into documents, and we add methods to support the loading and saving of RTFD files.

  1. In Project Builder, open the RTF Edit target (Project Edit Active Target, or Option-

    image with no caption

    -E), and select the Application Settings tab.

  2. Add a new document type entry as shown in Figure 11-11. Fill out the Document Type Information fields with the fields shown, and click Add. Don’t forget to set the Document Class field to MyDocument.

    Adding the rtfd file type to the RTF Edit application
    Figure 11-11. Adding the rtfd file type to the RTF Edit application
  3. Open MyDocument.m, and change the loadFileWrapperRepresentation:ofType: method as shown. This will allow RTF edit to open either RTF or RTFD files. Note that we use the name of the document type we set previously in step 2.

    - (BOOL)loadFileWrapperRepresentation:(NSFileWrapper *) ofType:(NSString *)type
    {
        if ([type isEqualToString:@"Rich Text with Attachments"]) {
                                 rtfData = [[NSAttributedString alloc]
                                     initWithRTFDFileWrapper:wrapper documentAttributes:nil];
                             } else {
            rtfData = [[NSAttributedString alloc]
                initWithRTF:[wrapper regularFileContents] documentAttributes:nil]; 
        }        
        if (textView) {                                                                
            [[textView textStorage]
                replaceCharactersInRange:NSMakeRange(0, [[textView string] length]);
                    withAttributedString:rtfData];
            [rtfData release];                                                         
         }
         return YES;
    }
  4. Next, change the fileWrapperRepresentationOfType: method so that the document can save its contents according to the type of data requested.

    - (NSFileWrapper *)fileWrapperRepresentationOfType:(NSString *)type
    {
        NSRange range = NSMakeRange(0, [[textView string] length]);
        if ([type isEqualToString:@"Rich Text with Attachments"]) {
                                 return [[textView textStorage] RTFDFileWrapperFromRange:range
                                                                      documentAttributes:nil];
                             } else {
            NSFileWrapper * wrapper = [[NSFileWrapper alloc]
                initRegularFileWithContents:[textView RTFFromRange:range]];
            return [wrapper autorelease];
        }
    }
  5. Finally, change the windowControllerDidLoadNib: method so that graphics can be added to the documents.

    - (void)windowControllerDidLoadNib:(NSWindowController *) aController
    {
        [super windowControllerDidLoadNib:aController];
        if (rtfData) {                                                              
            [[textView textStorage]
                replaceCharactersInRange:NSMakeRange(0, [[textView string] length]);
                    withAttributedString:rtfData];
            [rtfData release];
        }
        [textView setAllowsUndo:YES];   
        [textView setImportsGraphics:YES];                                           
    }
  6. Save the project (File Save, or

    image with no caption

    -S).

  7. Clean the project (Build Clean, or Shift-

    image with no caption

    -K).[18]

  8. Build and run (

    image with no caption

    -R) the application. Create a document, and drag an image into it. When you save the document, the Save panel will have a pull-down menu to select what kind of file you are saving, as shown in Figure 11-12. Be sure to select Rich Text with Attachments in order to save your image information .

The Spoken Word

The last thing we will add to our application has very little to do with rich text, but it’s fun and shows off one of the ways that Cocoa is integrated with other Mac OS X technologies. We’ll add a button that, when pressed, will speak the contents of a document to us using Mac OS X’s built-in Text-To-Speech engine.

  1. In Project Builder, edit MyDocument.h, and add the following action declaration:

    #import <Cocoa/Cocoa.h>
    
    @interface MyDocument : NSDocument
    {
        IBOutlet NSTextView * textView;
        NSData * dataFromFile;
        NSString * dataType;
    }
    - (IBAction)analyzeText:(id)sender;
    - (IBAction)removeFormatting:(id)sender;
    - (IBAction)speakText:(id)sender;
    @end
  2. Save the MyDocument.h file; then open MyDocument.nib in Interface Builder.

  3. Reparse the MyDocument.h file by dragging the MyDocument.h file from Project Builder to Interface Builder’s MyDocument.nib window.

  4. Add a button to our document interface, and name it Speak, as shown in Figure 11-13.

    Saving a file with attachments
    Figure 11-12. Saving a file with attachments
    Adding the Speak button to RTF Edit
    Figure 11-13. Adding the Speak button to RTF Edit
  5. Control-drag a connection from the Speak button to the File’s Owner object, and connect it to the speakText: method.

  6. Save (

    image with no caption

    -S) the nib file, and return to Project Builder.

  7. In Project Builder, edit the MyDocument.m file, and add the speakText: method as shown:

                            - (IBAction)speakText:(id)sender
                            {
                                [textView startSpeaking:sender];
                            }
  8. Save the project (File Save, or

    image with no caption

    -S).

  9. Build and run (

    image with no caption

    -R) the application. Type some text, then click on the Speak button. The built-in Text-to-Speech engine will start reading off what you typed.

Exercises

  1. Using Interface Builder, turn on image attachments and undo by removing the two lines of code in the awakeFromNib method that perform this duty.

  2. Set the ruler to appear automatically when a document window opens.

  3. Replace the Speak buttons with menu items.

  4. Add a number of characters line to the Analyze sheet.



[18] In some of the versions of Project Builder that we worked with while writing this book, there was a problem with adding document types unless you forced this cleaning step.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset