Drawing with layout manager in a non-flipped view
Drawing with layout manager in a non-flipped view
- Subject: Drawing with layout manager in a non-flipped view
- From: Antonio Nunes <email@hidden>
- Date: Fri, 2 Mar 2007 12:32:45 +0000
Hi,
I was going to post a question here, but with the help of an article
on macosxguru.net (http://www.macosxguru.net/article.php?
story=20040225223410877) found the solution. Still, although the
article is clear on the proceedings it doesn't provide much by way of
example, so I created a little test project to see if I could make it
work. Since searching the list archives on cocoabuilder turned up
surprisingly little info (other than this http://www.cocoabuilder.com/
archive/message/cocoa/2005/4/8/132608), and no code examples, I
thought I'd post my solution here. It also shows how to figure out
the required size of the text (which applies equally to flipped and
non-flipped views).
The trick is to be able to temporarily flip the view so that the
layout manager will draw the lines in correct rather than inverted
order. But that needs some special attention to ensure the text gets
drawn where you expect it to be drawn. To allow flipping the view
temporarily we need to be able to dynamically adjust the value
returned from "isFlipped", so we subclass NSView and add an ivar to
reflect the flipped state:
@interface TDTestView : NSView {
BOOL _flipped;
}
Here's an implementation with a contrived drawRect example. The Cocoa
frameworks will query the isFlipped method to see whether the view is
flipped, so we override NSView's isFlipped method with a getter for
our ivar. Providing a setter is optional: you could also directly
access the ivar within the drawRect method. That's dependent on needs
and coding preferences.
@interface TDTestView (Private)
- (void)setFlipped:(BOOL)flip;
- (NSSize)requiredSizeForText:(NSTextStorage *)text forMaxWidth:
(float)maxWidth;
@end
@implementation TDTestView
- (void)setFlipped:(BOOL)flip
{
_flipped = flip;
}
- (BOOL)isFlipped { return _flipped; }
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)rect {
// We need some sample text
NSTextStorage *text = [[NSTextStorage alloc] initWithString:
@"Accepting others as they are\nbrings a wonderful freedom\nto your
own mind."];
NSSize requiredSize = [self requiredSizeForText:text forMaxWidth:
1.0e6];
// Precalculate object bounds.
NSRect objectBounds = NSMakeRect(36, 85, requiredSize.width,
requiredSize.height);
// Perform some normal drawing
NSFrameRect(objectBounds);
// Set the view up for text layout manager drawing
// Flipping the view will make the layout manager draw the lines in
the correct order
[self setFlipped:YES];
// But it will draw the text upside down so we apply a transform to
NSAffineTransform *transform = [NSAffineTransform transform];
// invert the y-axis and
[transform scaleXBy:1.0 yBy:-1.0];
// 'invert' the drawing origin. Note that we effectively translate
by the object's height + TWICE the object's origin
[transform translateXBy:0.0 yBy: -(NSMinY(objectBounds) + NSMaxY
(objectBounds))];
// Depending on what you do in the next bit you may want to save the
current graphics state
// [[NSGraphicsContext currentContext] saveGraphicsState]; // but it
is cheaper not to
// Apply the transform
[transform concat];
// Set up the layout manager and its text container
NSLayoutManager *lm = [[NSLayoutManager allocWithZone:NULL] init];
NSTextContainer *tc = [[NSTextContainer allocWithZone:NULL]
initWithContainerSize:NSMakeSize(1.0e6, 1.0e6)];
[lm addTextContainer:tc];
[tc release]; // layout manager retains it
[tc setContainerSize:objectBounds.size];
// Add the layout manager to the NSTextStorage
[text addLayoutManager:lm];
[lm release]; // text storage retains it
// Force layout of the text and find out how much of it fits the
container
NSRange glyphRange = [lm glyphRangeForTextContainer:tc];
// Draw!
[lm drawBackgroundForGlyphRange:glyphRange
atPoint:objectBounds.origin];
[lm drawGlyphsForGlyphRange:glyphRange atPoint:objectBounds.origin];
[text removeLayoutManager:lm];
[text release];
// [[NSGraphicsContext currentContext] restoreGraphicsState];
[transform invert]; // if you saved and restored the graphics state
you don't need
[transform concat]; // bother about restoring and concatenating the
transform
// Return to normal
[self setFlipped:NO];
// And perform more drawing if you need to
NSFrameRect(NSInsetRect(objectBounds, -3, -3));
}
- (NSSize)requiredSizeForText:(NSTextStorage *)text forMaxWidth:
(float)maxWidth
{
NSSize minSize = NSMakeSize(10, 10); // adapt to your needs
NSSize returnSize = minSize;
unsigned textLength = [text length];
if (textLength > 0) {
NSSize maxSize = NSMakeSize(maxWidth, 1.0e6);
NSLayoutManager *lm = [[NSLayoutManager allocWithZone:NULL] init];
NSTextContainer *tc = [[NSTextContainer allocWithZone:NULL]
initWithContainerSize:maxSize];
[lm addTextContainer:tc];
[tc release]; // layout manager retains it
[text addLayoutManager:lm];
[lm release]; // text container retains it
// Force layout of the text
[lm glyphRangeForTextContainer:tc];
// and find out it's size
NSSize requiredSize = [lm usedRectForTextContainer:tc].size;
// We no longer need the layout manager
[text removeLayoutManager:lm];
// Make sure we are not smaller than the set minimum
if (requiredSize.width < minSize.width) {
requiredSize.width = minSize.width;
}
if (requiredSize.height < minSize.height) {
requiredSize.height = minSize.height;
}
returnSize = requiredSize;
}
return returnSize;
}
@end
Cheers,
António
-----------------------------------------
Touch is a language without words
-----------------------------------------
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden