Examples in Cocoa
The Two-Stage Creation pattern is used extensively by Cocoa, and you must adhere to it if you subclass any Cocoa classes. You must be aware of which initializer is the Designated Initializer to create a properly working subclass. Table 3.1 lists several prominent Cocoa classes that are frequently subclassed and identifies the Designated Initializers.
Table 3.1 Prominent Cocoa Classes and Their Designated Initializers
Class |
Designated Initializer |
NSObject |
-init |
NSView |
-initWithFrame: |
NSCell |
-initImageCell: and -initTextCell: |
NSControl |
-initWithFrame: |
NSDocument |
-init |
NSWindowController |
-initWithWindow: |
The NSCell class actually has two Designated Initializers and both must be overridden in subclasses, but the subclass is free to call either of the superclass’ Designated Initializers. As an example of an NSCell subclass, consider a class named MYLabeledBarCell.
MYLabeledBarCell instances each draw a label and a small bar that indicates values between 0.0 and 1.0. These cells can be used to indicate the percentage of battery charge remaining or the speed of mouse acceleration in a preference panel. The bar provides a quick indication of a value, and the label identifies the value. The bar’s value is set with the MYLabeledBarCell’s - (void)setBarValue:(float)aValue method, and the label is set with the - (void)setLabel:(NSString *)aLabel method or the –(void)setStringValue:(NSString *)aString method inherited from the NSCell class. Figure 3.1 shows several instances of MYLabeledBarCell, a subclass of NSCell.
Figure 3.1 The window shows a matrix of MYLabelBarCell instances within a scroll view.
The following implementation of MYLabeledBarCell calls the inherited - (id)initTextCell:(NSString *)aString from the implementations of both - (id)initImageCell:(NSImage *)anImage and -initTextCell:.
#import <Cocoa/Cocoa.h> @interface MYLabeledBarCell : NSCell { float barValue; // values in range 0.0 to 1.0 } // Overriden Designated Initializers - (id)initImageCell:(NSImage *)anImage; - (id)initTextCell:(NSString *)aString; // Overriden configuration - (BOOL)isOpaque; // Accessors - (void)setLabel:(NSString *)aLabel; - (NSString *)label; - (void)setBarValue:(float)aValue; - (float)barValue; // New drawing methods - (void)drawBarInRect:(NSRect)aRect; @end #import “MYLabeledBarCell.h” @implementation MYLabeledBarCell // Instances of this class store both a text label and a float value, // barValue. The label is drawn as an attributed string. A green bar is // drawn along the bottom of the cell based on the value of barValue // interpreted as a fraction of full length. If barValue is >= 1.0, the // bar is drawn full length. If barValue is <= 0, no bar is drawn. - (id)initImageCell:(NSImage *)anImage // Overriden Designated Initializer calls -initTextCell: { return [self initTextCell:@””]; } - (id)initTextCell:(NSString *)aString // Overriden Designated Initializer { self = [super initTextCell:aString]; if(nil != self) { [self setBarValue: 1.0f]; [self setFont:[NSFont labelFontOfSize:[NSFont labelFontSize]]]; } return self; } - (BOOL)isOpaque // Returns NO so that background will show through { return NO; } // Constants used to control drawing static const float BarHeightWithMargins = 4.0f; static const float BarMarginFraction = 0.25f; - (void)drawBarInRect:(NSRect)aRect // Draw a green bar that fills a portion of aRect specified by barValue { aRect.size.width *= barValue; [[NSColor greenColor] set]; NSRectFill(aRect); } - (NSSize)cellSizeForBounds:(NSRect)aRect // Overridden to return a size large enough for the label and the bar { NSSize cellSize = [super cellSizeForBounds:aRect]; // return rectangle large enough for both subcell and text return NSMakeSize(cellSize.width, cellSize.height + BarHeightWithMargins); } - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView // Overridden to draw the label and the bar { NSRect barRect; NSRect labelRect; // calculate the rectangles containing the bar and label NSDivideRect(cellFrame, &barRect, &labelRect, BarHeightWithMargins, NSMaxYEdge); // draw the label with margins around it [super drawInteriorWithFrame:labelRect inView:controlView]; // draw the bar with margins around it barRect = NSInsetRect(barRect, 0.0, BarHeightWithMargins * BarMarginFraction); [self drawBarInRect:barRect]; } - (void)setLabel:(NSString *)aLabel { // store the label as the receiver’s string value [self setStringValue:aLabel]; } - (NSString *)label { return [self stringValue]; } - (void)setBarValue:(float)aValue { // store the bar value in instance variable barValue = MIN(MAX(aValue, 0.0), 1.0); } - (float)barValue { return barValue; } @end
The Two-Stage Creation pattern plays an imports role in Cocoa’s Flyweight pattern described in Chapter 22 and the Singleton pattern in Chapter 13, the Archiving and Unarchiving pattern in Chapter 11, and the Class Clusters pattern in Chapter 25. The Accessors pattern simplifies the implementation of initializers as described in Chapter 10.