NSTabView Item adding an Icon in TabViewItem - objective-c

I subclassed NSTabView and added 5 TabViewItem, now i wanted to add an Icon along with the title in the NSTabViewItem,
Can anyone suggest me how to start, i am not getting any documentation except,
- (void)drawLabel:(BOOL)shouldTruncateLabel inRect:(NSRect)tabRect
Does that mean, if i override this method, i need to draw Icon and string my own,
For setting up the title , i am using following method,
[pTabViewItem setLabel:pLabelTitle];
Kind Regards
Rohan

Never Mind,
Following Code works for me,
- (void)drawLabel:(BOOL)shouldTruncateLabel inRect:(NSRect)tabRect{
// do we have an image to draw
NSImage *pImage = [pDelegate imageForCell];
[[NSGraphicsContext currentContext] saveGraphicsState];
NSAffineTransform* xform = [NSAffineTransform transform];
[xform translateXBy:0.0 yBy: tabRect.size.height];
[xform scaleXBy:1.0 yBy:-1.0];
[xform concat];
CGFloat x_Offset =0;
if(pImage){
[pImage drawInRect:NSMakeRect(tabRect.origin.x-8,-6,16, 16)fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0];
x_Offset = 16;
}
[[NSGraphicsContext currentContext] restoreGraphicsState];
[super drawLabel:shouldTruncateLabel inRect:tabRect];
}
Why transformation:
Image was showing inverted, so i need to transform,
Why offset:
Even after transformation, i need to adjust, the position so that it looks just before the title,
and guys, i while setting the title,
append a Space, so title will not overlap the image, i know this is ugly approach, but couldn’t get any other quick way to do it, if i draw the text myself then i need to take care of truncating also,
Thanks to those who looked at questions and answer
Kind Regards
Rohan

Related

Translating a view with a path (ObjectiveC, Cocoa)

The short story is that I would like the bounds (I think I mean bounds instead of frame) of a NSBezierPath to fill a view. Something like this:
To generate the above image I scaled/translated each point in my created path using the information from Covert latitude/longitude point to a pixels (x,y) on mercator projection. The problem is that this isn't scalable (I will be adding many more paths) and I want to easily add pan/zoom functionality to my view. Additionally, I want the stroke to remain the same regardless of scale (i.e. no fat boundaries when I zoom).
I think I want to generate a reusable path in some arbitrary reference frame (e.g. longitude and modified latitude) instead of generating a new path every time the window changes. Then I can translate/scale my view's coordinate system to fill the view with the path.
So I used Apple's geometry guide to to modify the view's frame. I got the translation right but scaling failed.
[self setBoundsOrigin:self.path.bounds.origin];
[self scaleUnitSquareToSize:NSMakeSize(1.5, 1.5)];
Then I tried a coordinate system transformation in my drawRect: method only to end up with a similar result.
NSAffineTransform* xform = [NSAffineTransform transform];
[xform translateXBy:(-self.path.bounds.origin.x) yBy:(-self.path.bounds.origin.y)];
[xform scaleXBy:1.5 yBy:1.5];
[xform concat];
Finally I tried manually setting the view bounds in drawRect: but the result was ugly and very slow!
I know I can also transform the NSBezierPath object and I think that would work, but I'd rather transform the view once instead of looping through and transforming each path every update. I think there's about three lines of code I'm missing that will do exactly what I'm looking for.
Edit:
Here's the drawRect: method I'm using:
- (void)drawRect:(NSRect)dirtyRect
{
// NSAffineTransform* xform = [NSAffineTransform transform];
// [xform translateXBy:-self.path.bounds.origin.x yBy:-self.path.bounds.origin.y];
// [xform scaleXBy:1.5 yBy:1.5];
// [xform concat];
[self drawBoundaries];
NSRect bounds = [self bounds];
[[NSColor blackColor] set];
[NSBezierPath fillRect:bounds];
// Draw the path in white
[[NSColor whiteColor] set];
[self.path stroke];
[[NSColor redColor] set];
[NSBezierPath strokeRect:self.path.bounds];
NSLog(#"path origin %f x %f",self.path.bounds.origin.x, self.path.bounds.origin.y);
NSLog(#"path bounds %f x %f",self.path.bounds.size.width, self.path.bounds.size.height);
}
I was able to get it to work using two transformations. I was trying to avoid this to reduce complexity and computation when I have many paths to transform and a window that zooms/pans.
- (void)transformPath:(NSBezierPath *)path
{
NSAffineTransform *translateTransform = [NSAffineTransform transform];
NSAffineTransform *scaleTransform = [NSAffineTransform transform];
[translateTransform translateXBy:(-self.path.bounds.origin.x)
yBy:(-self.path.bounds.origin.y)];
float scale = MIN(self.bounds.size.width / self.path.bounds.size.width,
self.bounds.size.height / self.path.bounds.size.height);
[scaleTransform scaleBy:scale];
[path transformUsingAffineTransform: translateTransform];
[path transformUsingAffineTransform: scaleTransform];
}

How to draw background image repeatedly with Mac OS

I am new in Mac OS programming. I would like to draw image repeatedly in background since my original image is small. Here is the code of drawing the image, but it seems enlarge the image which means it only draw one image instead of multiple.
// overwrite drawRect method of NSView
-(void)drawRect:(NSRect)dirtyRect{
[[NSImage imageNamed:#"imageName.png"] drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1];
[super drawRect:dirtyRect];
}
This should work for you...
// overwrite drawRect method of NSView
-(void)drawRect:(NSRect)dirtyRect{
[super drawRect:dirtyRect];
// Develop color using image pattern
NSColor *backgroundColor = [NSColor colorWithPatternImage:[NSImage imageNamed:#"imageName.png"]];
// Get the current context and save the graphic state to restore it once done.
NSGraphicsContext* theContext = [NSGraphicsContext currentContext];
[theContext saveGraphicsState];
// To ensure that pattern image doesn't truncate from top of the view.
[[NSGraphicsContext currentContext] setPatternPhase:NSMakePoint(0,self.bounds.size.height)];
// Set the color in context and fill it.
[backgroundColor set];
NSRectFill(self.bounds);
[theContext restoreGraphicsState];
}
Note: You may like to consider creating backgroundColor as part of the object for optimization as drawRect is called pretty often.

NSView drawRect interfering with subviews?

I have an nsview and i use draw rect to draw an image for background. It also has 3 subviews nsbuttons. The problem is, whenever the mouse is down on a button, the other buttons disappear. But when I remove the draw rect method, this doesn't happen. So I am guessing this has to do with the draw rect method for drawing images.
How can I avoid this?
Thanks.
EDIT:
Ok, i figured out where the problem is. Basically, I have an NSMenuItem, and I am putting a view inside it with 3 buttons. But in NSMenu, at the top, there's a padding of 4 pixels. So, basically, to remove that padding I used the solution provided here:
Gap above NSMenuItem custom view
From the solution there's a line in the drawRect method:
[[NSBezierPath bezierPathWithRect:fullBounds] setClip];
The moment, i remove this line, and the button behave properly. But then, the padding on top doesn't go away.
Here's my drawRect:
- (void) drawRect:(NSRect)dirtyRect {
[[NSGraphicsContext currentContext] saveGraphicsState];
NSRect fullBounds = [self bounds];
fullBounds.size.height += 4;
[[NSBezierPath bezierPathWithRect:fullBounds] setClip];
NSImage *background = [NSImage imageNamed:#"bg.png"];
[background drawInRect:fullBounds fromRect:NSZeroRect operation:NSCompositeCopy fraction:100.0];
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
The solution to the linked question doesn't include saving and restoring the graphics state, which is a good idea when you are modifying one that you didn't create. Give this a try:
- (void)drawRect:(NSRect)dirtyRect {
// Save the current clip rect that has been set up for you
[NSGraphicsContext saveGraphicsState];
// Calculate your fullBounds rect
// ...
// Set the clip rect
// ...
// Do your drawing
// ...
// Restore the correct clip rect
[NSGraphicsContext restoreGraphicsState]
Are you sure that those buttons are actually subviews, and not just placed over the view you're drawing?

Antialiased drawing in a CALayer

Greetings! I'm trying to draw a series of circles in a CALayer that resides in a zoomable UISCrollView. This is the layer that zooms on pinch. If i draw the circles using CAShapeLayer, then they zoom beautifully:
CAShapeLayer plotLayer = [CAShapeLayer layer];
plotLayer.bounds = self.bounds;
plotLayer.anchorPoint = CGPointZero;
plotLayer.position = CGPointZero;
CGMutablePathRef path = CGPathCreateMutable();
for (id one in many) {
CGRect ellipseRect = [one circleRect];
CGPathAddEllipseInRect(path, NULL, ellipseRect);
}
plotLayer.path = path
CFRelease(path);
[self.layer addSublayer:plotLayer];
[plotLayer setNeedsDisplay];
However, when i try to draw them with vanilla core graphics in my drawLayer:inContext: method, the circles get very jaggy (downright pro-aliased!) No amount of antialias jiggery-pokery seems to help:
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
CGContextSetAllowsAntialiasing(context, true);
CGContextSetShouldAntialias(context, true);
CGContextClip(context);
for (id one in many) {
CGRect ellipseRect = [one circleRect];
CGContextStrokeEllipseInRect(context, ellipseRect);
}
}
I'm trying to figure out what CAShapeLayer is doing to get such nice antialiasing so that (aside from my own edification) i can take full advantage of core graphics in my drawing rather than just the stroke/fill that i can get with CAShapeLayer. Am i just missing a transform somewhere?
Many thanks!
Short answer: contentsScale
Longer answer: (taken almost verbatim from pe8ter at Vector like drawing for zoomable UIScrollView):
You can get a better rendering by setting the contentScale property on the layer in question after zooming:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView
withView:(UIView *)view
atScale:(float)scale
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithBool:YES]
forKey:kCATransactionDisableActions];
uglyBlurryTextLayer.contentsScale = scale;
[CATransaction commit];
}
There's a (typically excellent) description by Brad Larson of when the actual drawing occurs (spoiler, it usually only occurs once despite how many transforms are applied) at So a CALayer does not contain a content bitmap of a view?. Based on this, i can only assume that setting contentsScale causes the layer to render again.
I haven't tried this but if your circles get jaggy when resizing the view, it might be because of the interpolation quality.
Have you tried setting the interpolation quality to high?
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);

Drawing a view hierarchy into a specific context in Cocoa

For part of my application I have a need to create an image of a certain view and all of its subviews.
To do this I'm creating a context that wraps a bitmap with the same-size as the view, but I'm unsure how to draw the view hierarchy into it. I can draw a single view just be setting the context and explicitly calling drawRect, but this does not deal with all of the subviews.
I can't see anything in the NSView interface that could help with this so I suspect the solution may lie at a higher level.
I found that writing the drawing code myself was the best way to:
deal with potential transparency issues (some of the other options do add a white background to the whole image)
performance was much better
The code below is not perfect, because it does not deal with scaling issues when going from bounds to frames, but it does take into account the isFlipped state, and works very well for what I used it for. Note that it only draws the subviews (and the subsubviews,... recursively), but getting it to also draw itself is very easy, just add a [self drawRect:[self bounds]] in the implementation of imageWithSubviews.
- (void)drawSubviews
{
BOOL flipped = [self isFlipped];
for ( NSView *subview in [self subviews] ) {
// changes the coordinate system so that the local coordinates of the subview (bounds) become the coordinates of the superview (frame)
// the transform assumes bounds and frame have the same size, and bounds origin is (0,0)
// handling of 'isFlipped' also probably unreliable
NSAffineTransform *transform = [NSAffineTransform transform];
if ( flipped ) {
[transform translateXBy:subview.frame.origin.x yBy:NSMaxY(subview.frame)];
[transform scaleXBy:+1.0 yBy:-1.0];
} else
[transform translateXBy:subview.frame.origin.x yBy:subview.frame.origin.y];
[transform concat];
// recursively draw the subview and sub-subviews
[subview drawRect:[subview bounds]];
[subview drawSubviews];
// reset the transform to get back a clean graphic contexts for the rest of the drawing
[transform invert];
[transform concat];
}
}
- (NSImage *)imageWithSubviews
{
NSImage *image = [[[NSImage alloc] initWithSize:[self bounds].size] autorelease];
[image lockFocus];
// it seems NSImage cannot use flipped coordinates the way NSView does (the method 'setFlipped:' does not seem to help)
// Use instead an NSAffineTransform
if ( [self isFlipped] ) {
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:0 yBy:NSMaxY(self.bounds)];
[transform scaleXBy:+1.0 yBy:-1.0];
[transform concat];
}
[self drawSubviews];
[image unlockFocus];
return image;
}
You can use -[NSView dataWithPDFInsideRect:] to render the entire hierarchy of the view you send it to into a PDF, returned as an NSData object. You can then do whatever you wish with that, including render it into a bitmap.
Are you sure you want a bitmap representation though? After all, that PDF could be (at least in theory) resolution-independent.
You can use -[NSBitmapImageRep initWithFocusedViewRect:] after locking focus on a view to have the view render itself (and its subviews) into the given rectangle.
What you want to do is available explicitly already. See the section "NSView Drawing Redirection API" in the 10.4 AppKit release notes.
Make an NSBitmapImageRep for caching and clear it:
NSGraphicsContext *bitmapGraphicsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:cacheBitmapImageRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:bitmapGraphicsContext];
[[NSColor clearColor] set];
NSRectFill(NSMakeRect(0, 0, [cacheBitmapImageRep size].width, [cacheBitmapImageRep size].height));
[NSGraphicsContext restoreGraphicsState];
Cache to it:
-[NSView cacheDisplayInRect:toBitmapImageRep:]
If you want to more generally draw into a specified context handling view recursion and transparency correctly,
-[NSView displayRectIgnoringOpacity:inContext:]