Objective C - OS X - Issue adding NSShadow to NSImageView - objective-c

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];

Related

UI Code in viewDidLayoutSubViews doesn't render correctly at runtime

I have recently been battling with my view controller to set the correct dimensions of a border on a UIView.
*edit, this is for a static tableview (i am using it as a form). I am aware of rounding cells in a .xib and reusing the cell however I am not reusing cells, this approach would not be useful to me.
I have a screen with a static tableview which is embedded within a UIView and displayed on a main View Controller. I had been having problems with the border as if I set it anywhere before viewDidAppear my method to add the border would use the wrong dimensions and display the border incorrectly. Here is the method I'm using to add a border to my UIView:
- (void)createCellBorder: (UIView *)view container:(UIView *)container {
CALayer *borderLayer = [CALayer layer];
CGRect adjustedContainer = CGRectMake(0, 0, container.frame.size.width - 20, container.frame.size.height);
CGRect borderFrame = CGRectMake(0, 0, (adjustedContainer.size.width), (view.frame.size.height));
NSLog(#"VIEW DIMENSIONS FOR %# %f, %f",view , view.frame.size.width, view.frame.size.height);
[borderLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[borderLayer setFrame:borderFrame];
[borderLayer setCornerRadius:view.frame.size.height / 2];
[borderLayer setBorderWidth:1.0];
[borderLayer setBorderColor:[kTextColor2 CGColor]];
[view.layer addSublayer:borderLayer];
}
Note * To fix some bugs with a single cell having the wrong dimensions (always the final cell in the table) I am setting the width of the border layer based on the UITableView it is contained in.
Anyway I eventually found out that instead of using viewDidAppear I should be using viewDidLayoutSubviews to set my UI Geometry as at this point in time the view has calculated its dimensions. This works a treat and my cells will display with rounded borders... However there is a problem with the rendering itself.
First lets have a look at the cells being rounded from the viewDidAppear.
Everything looks fine, this is exactly how I want my cells to look (I have removed the content of them to demonstrate the borders only). Lets have a look at the code I'm using to round the cells.
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self configureCells];
}
- (void)configureCells {
[self createCellBorder:_contentCellOne container:_profileTable];
[self createCellBorder:_contentCellTwo container:_profileTable];
[self createCellBorder:_contentCellThree container:_profileTable];
[self createCellBorder:_contentCellFour container:_profileTable];
[self createCellBorder:_contentCellFive container:_profileTable];
[self createCellBorder:_contentCellSix container:_profileTable];
[self createCellBorder:_contentCellSeven container:_profileTable];
}
Now lets change where I call configureCells from and see the difference.
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self configureCells];
}
All that has changed is where I call this method from. And in theory this should be the right place to configure my UI (and certainly more optimal than calling from viewDidAppear).
As you can see there is some quite noticeable 'distortion' around the corners of the cells. The pictures provided are from the simulator but I have tried this on a device and get the same result (maybe looks worse on my phone).
I have no idea why this is happening, and it means that whilst I have got my code out of the viewDidAppear, its just as unusable as before. I assume there will be a rendering option I can tweak in the viewDidLayoutSubViews but can't seem to find anything. All suggestions greatly appreciated!
In my opinion, you should create a subclass for UITableViewCell and configure border of cells inside layoutIfNeeded method.
I have create a demo project, you can check it TableViewCellBorder.

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).

Draw NSShadow inside a NSView

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...
}

Custom NSButton with semi-transparent background

I'm trying to create a custom NSButton with a 50% opaque black background and white text. To do this I've subclassed NSButton and overloaded DrawRect:
- (void) drawRect:(NSRect)dirtyRect
{
[self setBordered:NO];
//REMED since it has same effect as NSRectFill below
//[[self cell] setBackgroundColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.2]];
NSColor* backgroundColor = [NSColor colorWithCalibratedWhite:0 alpha:0.3f];
[backgroundColor setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
The white text appears fine but the button's background is always 100% opaque. The alpha value is not interpreted.
Any ideas? Thanks!
The default operation of NSRectFill() is copy which is not what you want. Replace it with
NSRectFillUsingOperation(dirtyRect, NSCompositeSourceAtop);
Another solution I found was to keep my code the same but turn on the Core Animation Layer for each button in Interface Builder. I don't know enough about Core Animation Layer to know why this worked. I had previously turned CAL off because it was making my fonts look very jagged.

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];
}