Chapter 17. Working with Fonts and NSAttributedString

The next step is to get the string to appear in our view. At the end of the chapter, your application will look like Figure 17.1. The character being displayed will change as you type.

Completed Application

Figure 17.1. Completed Application

NSFont

Overall, the class NSFont has basically only two types of methods:

  • Class methods for getting the font you want

  • Methods for getting metrics on the font, such as letter height

Commonly Used Methods in NSFont

  • + (NSFont *)fontWithName:(NSString *)fontName size:(float)fontSize
    
  • This method returns a font object. fontName is a family-face name, such as “HelveticaBoldOblique” or “Times-Roman.” If you use a fontSize of 0.0, this method uses the default User Font size.

    + (NSFont *)userFixedPitchFontOfSize:(float)fontSize 
    + (NSFont *)userFontOfSize:(float)fontSize 
    + (NSFont *)messageFontOfSize:(float)fontSize 
    + (NSFont *)toolTipsFontOfSize:(float)fontSize 
    + (NSFont *)titleBarFontOfSize:(float)fontSize
    
  • These methods return the user's default font for the corresponding string types. Once again, a size of 0.0 will get a font of the default size.

NSAttributedString

Sometimes you want to display a string that has certain attributes for a range of characters. As an example, suppose you want to display the string “Big Nerd Ranch,” and you want the letters 0 through 2 to be underlined, the letters 0 through 7 to be green, and the letters 9 through 13 to be subscripts.

When dealing with a range of numbers, Cocoa uses the struct NSRange. NSRange has two members: location and length are both integers. The location is the index of the first item, and the length is the number of items in the range. You can use the function NSMakeRange() to create an NSRange.

To create strings with attributes that remain in effect over a range of characters, Cocoa has NSAttributedString and NSMutableAttributedString. Here is how you could create the NSAttributedString just described:

NSMutableAttributedString *s;
s = [[NSMutableAttributedString alloc] 
           initWithString:@"Big Nerd Ranch"];

[s addAttribute:NSFontAttributeName
          value:[NSFont userFontOfSize:22]
          range:NSMakeRange(0, 14)];

[s addAttribute:NSUnderlineStyleAttributeName 
          value:[NSNumber numberWithInt:1]
          range:NSMakeRange(0,3)];

[s addAttribute:NSForegroundColorAttributeName 
          value:[NSColor greenColor] 
          range:NSMakeRange(0, 8)];

[s addAttribute:NSSuperscriptAttributeName 
          value:[NSNumber numberWithInt:-1]
          range:NSMakeRange(9,5)];

Once you have an attributed string, you can do lots of stuff with it.

[s drawInRect:[self bounds]];

// Put it in a text field
[textField setAttributedStringValue:s];

// Put it on a button
[button setAttributedTitle:s];

Figure 17.2 shows the result of this code's execution.

Using the Attributed String

Figure 17.2. Using the Attributed String

Here are the names of the global variables for the most commonly used attributes and what they mean:

NSFontAttributeName

A font object. By default, 12-point Helvetica.

NSForegroundColorAttributeName

A color. By default, black.

NSBackgroundColorAttributeName

A color. By default, no background drawn.

NSUnderlineColorAttributeName

A color. By default, the same as the foreground.

NSUnderlineStyleAttributeName

A number. By default, 0 (which means no underline).

NSSuperscriptAttributeName

A number. By default, 0 (which means no superscripting or subscripting).

NSShadowAttributeName

An NSShadow object. By default, nil (no shadow).

A list of all the attribute names can be found in <AppKit/NSAttributedString.h>.

The easiest way to create attributed strings is from a file. NSAttributedString can read and write the following file formats:

  • A stringYou read a text file.

  • RTFRich Text Format is a standard for text with multiple fonts and colors. In this case, You will read and set the contents of the attributed string with an instance of NSData.

  • RTFDThis is RTF with attachments. Besides the multiple fonts and colors of RTF, you can have images.

  • HTMLThe attributed string can do basic HTML layout, but you probably want to use the WebView for best quality. NSAttributedString reads HTML, but does not write it.

  • WordThe attributed string can read and write simple .doc files.

When you read a document in, you may want to know some things about it, such as the paper size. If you supply a place where the method can put a pointer to a dictionary, the dictionary will have all the extra information that it could get from the data. For example:

NSDictionary *myDict;
NSData *data = [NSData dataWithContentsOfFile:@"myfile.rtf"];
NSAttributedString *aString;
aString = [[NSAttributedString alloc] initWithRTF:data 
                               documentAttributes:&myDict];

If you don't care about the document attributes, just supply NULL.

Drawing Strings and Attributed Strings

Both NSString and NSAttributedString have methods that cause them to be drawn onto a view. NSAttributedString has the following methods:

  • - (void)drawAtPoint:(NSPoint)aPoint
    
  • Draws the receiver. aPoint is the lower-left corner of the string.

    - (void)drawInRect:(NSRect)rect
    
  • Draws the receiver. All drawing occurs inside rect. If rect is too small for the string to fit, the drawing is clipped to fit inside rect.

    - (NSSize)size
    
  • Returns the size that the receiver would be if drawn.

NSString has analogous methods. With NSString, you need to supply a dictionary of attributes to be applied for the entire string.

  • - (void)drawAtPoint:(NSPoint)aPoint
         withAttributes:(NSDictionary *)attribs
    
  • Draws the receiver with the attributes in attribs.

    - (void)drawInRect:(NSRect)aRect
        withAttributes:(NSDictionary *)attribs
    
  • Draws the receiver with the attributes in attribs.

    - (NSSize)sizeWithAttributes:(NSDictionary *)attribs
    
  • Returns the size that the receiver would be if drawn with the atttibutes in attribs.

Making Letters Appear

Open BigLetterView.h. Add an instance variable to hold the attributes dictionary. Declare the methods that you are about to implement:

#import <Cocoa/Cocoa.h>

@interface BigLetterView : NSView
{
    NSColor *bgColor;
    NSString *string;
    NSMutableDictionary *attributes;
}
- (void)prepareAttributes;
- (void)drawStringCenteredIn:(NSRect)bounds;
- (void)setBgColor:(NSColor *)c;
- (void)setString:(NSString *)c;
- (NSString *)string;

@end

Open BigLetterView.m. Create a method that creates the attributes dictionary with a font and a foreground color:

- (void)prepareAttributes
{
 attributes = [[NSMutableDictionary alloc] init];

 [attributes setObject:[NSFont fontWithName:@"Helvetica" 
                          size:75] 
                forKey:NSFontAttributeName];

 [attributes setObject:[NSColor redColor] 
                forKey:NSForegroundColorAttributeName];
}

In the initWithFrame: method, call the new method:

- (id)initWithFrame:(NSRect)rect 
{
    if (self = [super initWithFrame:rect]) {
      NSLog(@"initializing view");
      [self prepareAttributes];
      [self setBgColor:[NSColor yellowColor]];
      [self setString:@" "];
    }
    return self;
}

In the setString: method, tell the view that it needs to redisplay itself:

- (void)setString:(NSString *)c
{
    c = [c copy];
    [string release];
    string = c;
    NSLog(@"The string: %@", string);
    [self setNeedsDisplay:YES];
}

Create a method that will display the string in the middle of a rectangle:

- (void)drawStringCenteredIn:(NSRect)r
{
    NSPoint stringOrigin;
    NSSize stringSize;
    
    stringSize = [string sizeWithAttributes:attributes];
    stringOrigin.x = r.origin.x + (r.size.width - stringSize.width)/2;
    stringOrigin.y = r.origin.y + (r.size.height - stringSize.height)/2;
    [string drawAtPoint:stringOrigin withAttributes:attributes]; 
}

Call that method from inside your drawRect: method:

- (void)drawRect:(NSRect)rect
{ 
    NSRect bounds = [self bounds];
    [bgColor set];
    [NSBezierPath fillRect:bounds];

    [self drawStringCenteredIn:bounds];
    if ([[self window] firstResponder] == self) {
        [[NSColor keyboardFocusIndicatorColor] set];
        [NSBezierPath setDefaultLineWidth:4.0];
        [NSBezierPath strokeRect:bounds];
    }
}

Make sure you release the attributes dictionary in the dealloc method:

- (void)dealloc
{
    [string release];
    [attributes release];
    [bgColor release];
    [super dealloc];
}

Build and run the application. Note that keyboard events go to your view unless they trigger a menu item. Try pressing Command-w. It should close the window (even if your view is the first responder for the key window).

Getting Your View to Generate PDF Data

All of the drawing commands can be converted into PDF by the AppKit framework. The PDF data can be sent to a printer or to a file. Note that the PDF will always look as good as possible on any device, because it is resolution independent.

You have already created a view that knows how to generate PDF data to describe how it is supposed to look. Getting the PDF data into a file is really quite easy. NSView has the following method:

- (NSData *)dataWithPDFInsideRect:(NSRect)aRect

This method creates a data object and then calls drawRect:. The drawing commands that would usually go to the screen instead go into the data object. Once you have this data object, you simply save it to a file.

Open BigLetterView.m and add a method that will create a save panel as a sheet:

- (IBAction)savePDF:(id)sender
{
    NSSavePanel *panel = [NSSavePanel savePanel];
    [panel setRequiredFileType:@"pdf"];
    [panel beginSheetForDirectory:nil
                             file:nil
                   modalForWindow:[self window]
                    modalDelegate:self
                   didEndSelector:
                     @selector(didEnd:returnCode:contextInfo:)
                     contextInfo:NULL];
}

When the user has chosen the filename, the method didEnd:returnCode: contextInfo: will be called. Implement this method in BigLetterView.m:

- (void)didEnd:(NSSavePanel *)sheet
    returnCode:(int)code
    contextInfo:(void *)contextInfo
{
    NSRect r;
    NSData *data;
    
    if (code == NSOKButton) {
      r = [self bounds];
      data = [self dataWithPDFInsideRect:r];
      [data writeToFile:[sheet filename] atomically:YES];
    }
}

Also, declare these methods in the BigLetterView.h file:

- (IBAction)savePDF:(id)sender;

- (void)didEnd:(NSSavePanel *)sheet
    returnCode:(int)code
   contextInfo:(void *)contextInfo;

Open the nib file. Drag in BigLetterView.h so that savePDF: will appear as one of the actions. Select the Save As… item under the File menu. Relabel it Save PDF…. (You may delete all of the other menu items from the menu, if you wish.) Make the Save PDF… menu item trigger the BigLetterView's savePDF: method (Figure 17.3).

Connect Menu Item

Figure 17.3. Connect Menu Item

Save and build the application. You should be able to generate a PDF file and view it in Preview (Figure 17.4).

Completed Application

Figure 17.4. Completed Application

You will notice that multi-keystroke characters (like “é”) are not handled by your BigLetterView. To make this possible, you would need to add several methods that the NSInputManager uses. This topic is beyond the scope of this book (I just wanted to show you how to get keyboard events), but you can learn about it in Apple's discussion of NSInputManager (/Developer/Documentation/ Cocoa/Conceptual/InputManager/index.html).

For the More Curious: NSFontManager

Sometimes you will have a font that is good but would be perfect if it were bold or italicized or condensed. NSFontManager can be used to make this sort of conversion. You can also use a font manager to change the size of the font.

For example, imagine you have a font and would like a similar font, but bold. Here is the code:

fontManager = [NSFontManager sharedFontManager]; 
boldFont = [fontManager convertFont:aFont toHaveTrait:NSBoldFontMask];

Challenge 1

Give the letter a shadow. The NSShadow class has the following methods:

- (id)init;    
- (void)setShadowOffset:(NSSize)offset;
- (void)setShadowBlurRadius:(float)val;
- (void)setShadowColor:(NSColor *)color;

Challenge 2

Add the Boolean variables: bold and italic to your BigLetterView. Add check boxes that toggle these variables. If bold is YES, make the letter appear in boldface; if italic is YES, make the letter appear in italics.

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

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