Draw NSShadow inside a NSView - objective-c

I'm trying to draw a NSShadow on the background of a NSView. I want to use it as a replacement for NSGradient, as I need to support Mac OS X Tiger. How may I do that? I know this must be pretty easy and I must be making some mistake.
Thanks!

The easiest approach may be to just set the shadow properties for the view's layer. If you have a NSView* named view, it'd be something like:
[[view layer] setShadowOpacity:0.5];
Setting the shadow opacity to something greater than 0 will make the shadow visible. The shadow drawn will be similar to the view's alpha channel, so whatever you draw in the view will have a shadow. There are several other shadow attributes that you can set, such as the blur radius. Take a look at the CALayer reference page for more.
If you must use NSShadow, then just set up a shadow before you do your drawing:
- (void)drawRect:(NSRect)rect
{
NSShadow *shadow = [[[NSShadow alloc] init] autorelease];
[shadow setShadowBlurRadius:3.0];
[shadow setShadowOffset:NSMakeSize(0.0, 5.0)];
[shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.6]];
[shadow set];
// continue with your drawing...
}

Related

Objective C - OS X - Issue adding NSShadow to NSImageView

I am trying to add a shadow to a NSImageView on an MAC application.
I created a custom NSImageView class "ShadowView.h" and modified the drawRect: like so:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowBlurRadius:5];
[shadow setShadowOffset:NSMakeSize(30.0, 3.0)];
[shadow setShadowColor:[NSColor redColor]];
[shadow set];
[self setWantsLayer:YES];
[self setShadow:shadow];
}
However nothing happens. Also, when I debug I can see the above code being called. I looked at this question from 5 years ago but it seems to not work anymore
Adding a Shadow to a NSImageView
Thank you!
When adding a shadow to a view, that view's superview also needs to have layer-backing enabled. If it doesn't, the view's shadow gets clipped at its own bounds, as seen in this sample app:
Make sure you call -setWantsLayer:YES on your view's superview (or check the "Core Animation Layer" checkbox in Interface Builder) in order to make sure the shadow is completely visible:
You should set these somewhere else like, initWithFrame: take them out of the drawRect:
[self setWantsLayer:YES];
[self setShadow:shadow];

How to change the focus ring color?

In the draw function of the NSImageView,I want to set my custom focus ring color,not the default blue color.I tried as follows code
NSSetFocusRingStyle(NSFocusRingOnly);
[[NSColor redColor] setStroke];
NSRectFill([self bounds]);
But the color is still default blue color.How can I solve this problem?
update:
The NSShadow class help me to accomplish adding focus ring.It's easy to change the shadow color.
NSShadow *shadow = [[[NSShadow alloc] init] autorelease];
[shadow setShadowBlurRadius:5];
[shadow setShadowOffset:NSMakeSize(0.0, 0.0)];
[shadow setShadowColor:[NSColor redColor]];
[shadow set];
I code this in the NSImageView's draw function.
The only way to solve this "problem" is to draw the focus ring yourself. The NSFocusRingStyle code is designed to make it easy for you to draw a focus ring in the user's selected focus style, which includes them being able to set the focus ring color.
It is by design that you cannot easily change the focus ring color, as it is part of the OS theme and thus should be under the user's control (according to the HIG). If you have a special case, such as needing to adaptively color the focus ring based on the contents of the image, you will have to create your own focus ring from scratch.

drawRect: crash by big rect after zomming

My UIView structure:
I have a "master" UIView (actually UIScrollView, but it's only purpose scrolling per pagination).
As a subView on this "master" I have my "pageView" (subclass of UIScrollView). This UIScrollView can have any content (e.g. a UIImageView).
The "master" has another subView: PaintView (subclass of UIView). With this view, I track the finger movements and draw it.
The structure looks like this:
[master.view addSubview: pageView];
[master.view addSubview: paintView];
I know when the user zooms in (pageView is responsible for this) and over delegate/method calls I change the frame of paintView according to the zoom change, during the zoom action.
After zooming (scrollViewDidEndZooming:withView:atScale) I call a custom redraw method of paintView.
Redraw method and drawRect:
-(void)redrawForScale:(float)scale {
for (UIBezierPath *path in _pathArray) {
//TransformMakeScale...
}
[self setNeedsDisplay];
}
-(void)drawRect:(CGRect)rect {
for (UIBezierPath *path in _pathArray) {
[path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
}
The problem:
When I zoom-in, I receive a memory warning and the app crashes.
In the Allocations profiler I can see that the app own a lot of memory, but I can't see why.
When I don't call 'setNeedDisplay' after my 'redrawForScale' method the app isn't crashing.
When I log the rect in drawRect I see values like this: {{0, 0.299316}, {4688, 6630}}.
The problem is (re)drawing such a huge CGRect (correct me if this is wrong).
The solution for me is to avoid calling a custom drawRect method. In some stackoverflow answers (and I think somewhere in the Appls docs) it was mentioned that a custom drawRect: will reduce the performance (because iOS can't do some magic on a custom drawRect method).
My solution:
Using a CAShapeLayer. For every UIBezierPath I create a CAShapeLayer instance, add the UIBezierPath to the path attribute of the layer and add the layer as a sublayer to my view.
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] initWithLayer:self.layer];
shapeLayer.lineWidth = 10.0;
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.path = myBezierPath.CGPath;
[self.layer addSublayer:shapeLayer];
With this solution I don't need to implement drawRect. So the App won't crash, even with a big maximumZoomScale.
After changing the UIBezierPath (translate, scale, change color) I need to set the changed bezierPath to the shapeLayer again (set the path attribute of shapeLayer again).

NSScrollView messes up NSGradient (corruption)

I have a custom box that I've made that is a subclass of NSBox. I override the drawRect: method and draw a gradient in it like this (assuming I already have a start & end color):
-(void)drawRect:(NSRect)dirtyRect {
NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:start endingColor:end];
[gradient drawInRect:dirtyRect angle:270];
[gradient release];
}
Now this box is added as a subview of a prototype view for a NSCollectionView. In the view's original state it looks like this:
And after scrolling the view out of sight and back in again, it looks like this:
Why is my gradient getting corrupted like that, and how can I fix it? Thanks!
That dirtyRect argument doesn’t necessarily represent the entire box. If Cocoa decides that only a subframe of the original frame needs (re)drawing, dirtyRect represents only that subframe. If you’ve drawn a gradient for the entire frame and then (re)draw the same gradient for a subframe, it's likely they won't match.
Try:
[gradient drawInRect:[self bounds] angle:270];
instead.
One further note: it looks like your gradient object could be cached instead of being created/released inside -drawRect:.
Your problem is you're drawing in dirtyFrame, not the entire rectangle of the box. I have no idea if this is correct, but try this:
-(void)drawRect:(NSRect)dirtyRect {
NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:start endingColor:end];
[gradient drawInRect:[self bounds] angle:270];
[gradient release];
}

Odd problem with NSImage -lockFocusFlipped:

I'm using NSImage's -lockFocusFlipped: method to do some drawing into an image. My code looks like this:
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(256, 256)];
[image lockFocusFlipped:YES];
NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowColor:[NSColor blackColor]];
[shadow setShadowBlurRadius:6.0];
[shadow setShadowOffset:NSMakeSize(0, 3)];
[shadow set];
NSRect shapeRect = NSMakeRect(0, 0, 256, 100);
[[NSColor redColor] set];
NSRectFill(shapeRect);
[image unlockFocus];
This code works to a certain point. I can confirm that the context is indeed flipped because [[NSGraphicsContext currentContext] isFlipped] returns YES, and also because shapeRect is drawn at the right position (using the top left corner as the origin). That said, the NSShadow does not seem to respect the flipped status of the context. Setting the shadow offset to (0, 3) should move the shadow down when the context is flipped, but it actually moves it up (which is what would happen in a standard non-flipped context).
This problem seems specific to -lockFocusFlipped, because when I'm drawing using this same code into a CALayer with a flipped coordinate system, the shadow is drawn just fine (respecting the flip). Documentation on -lockFocusFlipped also seems to be quite vague. This is all it says in the NSImage class documentation:
Prepares the image to receive drawing commands using the specified flipped state.
And I also found this note in the Snow Leopard AppKit Release Notes:
There are cases, for example drawing directly via NSLayoutManager, that require a flipped context. To cover this case, we add
- (void)lockFocusFlipped:(BOOL)flipped;
This doesn't alter the state of the image itself, only the context on which focus is locked. It means that (0,0) is at the top left and positive along the Y-axis is down in the locked context.
None of the docs seem to explain NSShadow's behaviour in this case. And through further testing, it seems NSGradient does not seem to respect the flipped state of the drawing context used by NSImage either.
Any insight is greatly appreciated :-)
From the NSShadow class reference:
Shadows are always drawn in the default user coordinate space, regardless of any transformations applied to that space. This means that rotations, translations and other transformations of the current transformation matrix (the CTM) do not affect the resulting shadow.
And that's what flipping ultimately is: Translate up, scale back the other way.
There's no such statement for NSGradient, so I'd suggest filing a bug about that one.