NSTextView and NSTextContainer size & clipping area - cocoa

I found very interesting behavior of text container inside NSTextView. When i set size of container so it less than size of NSTextView frame and try
draw any figures (like lines, rectangles) in NSTextView drawRect: , all my figures clipped to size of text container.
So, frame size of NSTextView "allows" me to use it for drawing, but seems that is limited to container size.
If there are any possibility to draw inside text view but outside of text container ?
Code in Custom NSTextView - (void) drawRect:
[super drawRect:dirtyRect];
NSBezierPath* aPath = [NSBezierPath bezierPath];
[aPath moveToPoint:NSMakePoint(100, 100)];
[aPath lineToPoint:NSMakePoint(500, 100)];
[aPath stroke];
Custom textview resize policy set, so it resizes in all dimensions with container. This is code for custom NSTextView
- (void) setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize];
NSTextContainer *container = [self textContainer];
newSize.width -= 200;
[container setContainerSize:newSize];
}

Thanks Ross Carter for advice:
Try wrapping the call to super like this:
[NSGraphicsContext saveGraphicsState];
[super drawRect:rect];
[NSGraphicsContext restoreGraphicsState];

Related

Rounded NSImage corners in a NSButtonCell

I have a NSMatrix with a couple of NSButtons in it which have no Text but are only images. One of the images is downloaded from the internet and I would like to have it rounded corners in my OS X application.
I found one answer which nearly is what I was looking for: How to draw a rounded NSImage but sadly it acts crazy when I use it:
// In my NSButtonCell subclass
- (void)drawImage:(NSImage*)image withFrame:(NSRect)imageFrame inView:(NSView*)controlView
{
// [super drawImage:image withFrame:imageFrame inView:controlView];
[NSGraphicsContext saveGraphicsState];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:imageFrame xRadius:5 yRadius:5];
[path addClip];
[image drawInRect:imageFrame fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
}
The problem is that if a image is partly transparent (PNG) then it is completly destroyed and I only see a couple of white pixels on a black background.
And if the image is not transparent then it gets the rounded corners but is rotated 180° and I don't know why.
Any suggestions?
You need to make sure you set the image's size correctly before drawing it, and you should use the NSImage method drawInRect:fromRect:operation:fraction:respectFlipped:hints: to ensure the image is drawn the correct way up:
- (void)drawImage:(NSImage*)image withFrame:(NSRect)imageFrame inView:(NSView*)controlView
{
// [super drawImage:image withFrame:imageFrame inView:controlView];
[NSGraphicsContext saveGraphicsState];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:imageFrame xRadius:5 yRadius:5];
[path addClip];
//set the size
[image setSize:imageFrame.size];
//draw the image
[image drawInRect:imageFrame fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil];
[NSGraphicsContext restoreGraphicsState];
}
The image should draw correctly if you do this, even if it's a translucent PNG image.

NSScrollView background image in Lion

I'm working on a Mac application with an NSScrollView, and I want the NSScrollView to have a custom background image. I used this code in the custom documentView NSView subclass:
- (void)drawRect:(NSRect)rect {
[[NSColor colorWithPatternImage:[NSImage imageNamed:#"wood.jpg"]] set];
NSRectFill(rect);
}
That displays a pattern image as a background for the documentView.
But now in Mac OS X Lion, the NSScrollView bounces when scrolling further than possible, showing ugly white space. How can I make the white space also being covered by the background image?
Instead of overriding drawRect:, use the scroll view's setBackgroundColor: method, passing the NSColor you created with the pattern image.
You should subclass use NSScrollView setBackgroundColor, but then you should subclass NSClipView like this to pin the texture origin to the top:
#implementation MYClipView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
if (self.drawsBackground)
{
NSGraphicsContext* theContext = [NSGraphicsContext currentContext];
[theContext saveGraphicsState];
float xOffset = NSMinX([self convertRect:[self frame] toView:nil]);
float yOffset = NSMaxY([self convertRect:[self frame] toView:nil]);
[theContext setPatternPhase:NSMakePoint(xOffset, yOffset)];
NSColor* color = self.backgroundColor;
[color set];
NSRectFill([self bounds]);
[theContext restoreGraphicsState];
}
// Note: We don't call [super drawRect:dirtyRect] because we don't need it to draw over our background.
}
+ (void)replaceClipViewInScrollView:(NSScrollView*)scrollView
{
NSView* docView = [scrollView documentView]; //[[scrollView documentView] retain];
MYClipView* newClipView = nil;
newClipView = [[[self class] alloc] initWithFrame:[[scrollView contentView] frame]];
[newClipView setBackgroundColor:[[scrollView contentView] backgroundColor]];
[scrollView setContentView:(NSClipView*)newClipView]; [scrollView setDocumentView:docView];
// [newClipView release];
// [docView release];
}
#end
And call + (void)replaceClipViewInScrollView:(NSScrollView*)scrollView with the NSScrollView instance.
Put your drawRect code in an NSScrollView subclass. In IB, change the NSScrollView to use your custom subclass instead of NSScrollView. Also make sure to uncheck Draw Background in the scroll view's attributes inspector.

Prevent NSButtonCell image from drawing outside of its containing NSScrollView

I have asked (and answered) a very similar question before. That question was able to be solved because I knew the dirtyRect, and thus, knew where I should draw the image. Now, I am seeing the same behavior with an subclassed NSButtonCell:
In my subclass, I am simply overriding the drawImage:withFrame:inView method to add a shadow.
- (void)drawImage:(NSImage *)image withFrame:(NSRect)frame inView:(NSView *)controlView {
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
[ctx saveGraphicsState];
// Rounded Rect
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame cornerRadius:kDefaultCornerRadius];
NSBezierPath *shadowPath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 0.5, 0.5) cornerRadius:kDefaultCornerRadius]; // prevents the baground showing through the image corner
// Shadow
NSColor *shadowColor = [UAColor colorWithCalibratedWhite:0 alpha:0.8];
[NSShadow setShadowWithColor:shadowColor
blurRadius:3.0
offset:NSMakeSize(0, -1)];
[[NSColor colorWithCalibratedWhite:0.635 alpha:1.0] set];
[shadowPath fill];
[NSShadow clearShadow];
// Background Gradient in case image is nil
NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[UAColor grayColor] endingColor:[UAColor lightGrayColor]];
[gradient drawInBezierPath:shadowPath angle:90.0];
[gradient release];
// Image
if (image) {
[path setClip];
[image drawInRect:frame fromRect:CGRectZero operation:NSCompositeSourceAtop fraction:1.0];
}
[ctx restoreGraphicsState];
}
It seems all pretty standard, but the image is still drawing outside of the containing scrollview bounds. How can I avoid this?
You shouldn't call -setClip on an NSBezierPath unless you really mean it. Generally you want ‑addClip, which adds your clipping path to the set of current clipping regions.
NSScrollView works by using its own clipping path and you're blowing that path away before drawing your image.
This tripped me up for a long time too.

Rounded rect on NSView that clips all containing subviews

I am creating a NSView subclass that has rounded corners. This view is meant to be a container and other subviews will be added to it. I am trying to get the rounded corners of the NSView to clip all of the subview's corners as well, but am not able to get it.
- (void)drawRect:(NSRect)dirtyRect {
NSRect rect = [self bounds];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:self.radius yRadius:self.radius];
[path addClip];
[[NSColor redColor] set];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
The red is just for example. If I add a subview to the rect, The corners are not clipped:
How can I achieve this?
Using Core Animation layers will clip sublayers correctly.
In your container NSView subclass:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layer = _layer; // strangely necessary
self.wantsLayer = YES;
self.layer.masksToBounds = YES;
self.layer.cornerRadius = 10.0;
}
return self;
}
You can do it in the interface builder without subclassing adding User Defined Runtime Attributes"
Have you tried clipping with layers?
self.layer.cornerRadius = self.radius;
self.layer.masksToBounds = YES;
Ah, sorry, somehow I've missed that you were talking about NSView, not UIView. It would be hard to clip NSView subviews in all cases because it seems that most of Cocoa standard views set their own clipping path. It might be easier to layout subviews with some paddings and avoid need for clipping.

Image inside NSScrollView drawing on top of other views

I have a custom NSView that lives inside of a NSScrollView that is in a NSSplitView. The custom view uses the following drawing code:
- (void)drawRect:(NSRect)dirtyRect {
NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
[ctx saveGraphicsState];
// Rounded Rect
NSRect rect = [self bounds];
NSRect pathRect = NSMakeRect(rect.origin.x + 3, rect.origin.y + 6, rect.size.width - 6, rect.size.height - 6);
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:pathRect cornerRadius:kDefaultCornerRadius];
// Shadow
[NSShadow setShadowWithColor:[NSColor colorWithCalibratedWhite:0 alpha:0.66]
blurRadius:4.0
offset:NSMakeSize(0, -3)];
[[NSColor colorWithCalibratedWhite:0.196 alpha:1.0] set];
[path fill];
[NSShadow clearShadow];
// Background Gradient
NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[UAColor darkBlackColor] endingColor:[UAColor lightBlackColor]];
[gradient drawInBezierPath:path angle:90.0];
[gradient release];
// Image
[path setClip];
NSRect imageRect = NSMakeRect(pathRect.origin.x, pathRect.origin.y, pathRect.size.height * kLargeImageRatio, pathRect.size.height);
[self.image drawInRect:imageRect
fromRect:NSZeroRect
operation:NSCompositeSourceAtop
fraction:1.0];
[ctx restoreGraphicsState];
[super drawRect:dirtyRect];
}
I have tried every different type of operation but the image still draws on top of the other half of the NSSplitView like so:
…instead of drawing under the NSScrollView. I think this has to do with drawing everything instead of the dirtyRect only, but I don't know how I could edit the image drawing code to only draw the part of it that lies in the dirtyRect. How can I either prevent it from drawing on top, or only draw the dirty rect for this NSImage?
I finally got it. I don't know if it is optimal yet, but I will find out when doing performance testing. I just had to figure out the intersection of the image rect and the dirty rect using NSIntersectionRect, then figuring out which subpart of the NSImage to draw for the drawInRect:fromRect:operation:fraction: call.
Here is the important part:
NSRect imageRect = NSMakeRect(pathRect.origin.x, pathRect.origin.y, pathRect.size.height * kLargeImageRatio, pathRect.size.height);
[self.image setSize:imageRect.size];
NSRect intersectionRect = NSIntersectionRect(dirtyRect, imageRect);
NSRect fromRect = NSMakeRect(intersectionRect.origin.x - imageRect.origin.x,
intersectionRect.origin.y - imageRect.origin.y,
intersectionRect.size.width,
intersectionRect.size.height);
[self.image drawInRect:intersectionRect
fromRect:fromRect
operation:NSCompositeSourceOver
fraction:1.0];

Resources