Shadow Effect not working with Resizable NSView - macos

I have written below code for shadow effect for my NSView.
[_nsview setWantsLayer:YES];
_nsview.layer.masksToBounds = NO;
_nsview.layer.cornerRadius = 5;
_nsview.layer.shadowOffset = CGSizeMake(.3f, -.3f);
_nsview.layer.shadowRadius = 10;
_nsview.layer.shadowOpacity = 0.20;
_nsview.layer.shadowColor = [NSColor blackColor].CGColor;
_nsview is outlet of that NSView. Above code works perfectly and gives shadow effect...But problem is that after resizing _nsview shadow getting hide.

Use NSShadow instead:
[_childView setWantsLayer:YES];
_childView.layer.backgroundColor = [NSColor whiteColor].CGColor;
_childView.layer.cornerRadius = 5;
NSShadow *dropShadow = [[NSShadow alloc] init];
[dropShadow setShadowColor:[NSColor colorWithWhite:0.1 alpha:0.6]];
[dropShadow setShadowOffset:NSMakeSize(0, -5)];
[dropShadow setShadowBlurRadius:5];
_childView.shadow = dropShadow;

Unless you're using a layer-hosting view (note: different from a layer-backed view) then you should assume that NSView can change layer properties at any time behind the scenes. Shadows and transforms are the most common properties to change outside of the developer's control.
Here is some useful reading:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html
https://www.objc.io/issues/14-mac/appkit-for-uikit-developers/

Related

CAShapeLayer problems - not drawing in nstableview

I customize a view-based tableview, and in the customize NSView, I write in the init:
NSRect testRect = imageViewRect;
CGMutablePathRef roundPath = CGPathCreateMutable();
CGPathAddArc(roundPath, NULL,
[self arcCenter:testRect].x,
[self arcCenter:testRect].y,
ArcRadius,
2 * M_PI + M_PI_2,
M_PI_2,
YES);
self.backgroundLayer = [CAShapeLayer layer];
self.backgroundLayer.frame = testRect;
self.backgroundLayer.path = roundPath;
self.backgroundLayer.strokeColor = [[NSColor blueColor] CGColor];
self.backgroundLayer.fillColor = nil;
self.backgroundLayer.lineWidth = 5.0f;
self.backgroundLayer.lineJoin = kCALineJoinBevel;
[self.layer addSublayer:self.backgroundLayer];
[self.backgroundLayer setFillColor:[NSColor yellowColor].CGColor];
but the layer is not shown, I really don't know where is the problem.
I wrote this in a view, and load the view in a window, the layer is showed correctly.
Have you turned your view 'self' a layer backed view? You will need to say
self.wantsLayer = true
before you can do any layer related manipulation.
Quoting documentation-
Setting the value of this property to true turns the view into a
layer-backed view—that is, the view uses a CALayer object to manage
its rendered content. Creating a layer-backed view implicitly causes
the entire view hierarchy under that view to become layer-backed.
Thus, the view and all of its subviews (including subviews of
subviews) become layer-backed. The default value of this property is
false.

drawLayer:inContext: draws background over content when using Layer-Hosting NSView

This is causing me some pain...
I wish to use layer-hosting views in my app and I'm having this weird problem.
Here is a simple example. Simply implemented by creating a new project in Xcode and entering the following in the AddDelegate: (after adding QuartzCore to the project):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSView *thisView = [[NSView alloc] initWithFrame:CGRectInset([self.window.contentView bounds], 50, 50)];
[thisView setLayer:[CALayer layer]];
[thisView setWantsLayer:YES];
thisView.layer.delegate = self;
thisView.layer.backgroundColor = CGColorCreateGenericRGB(1,1,0,1);
thisView.layer.anchorPoint = NSMakePoint(0.5, 0.5);
[self.window.contentView addSubview:thisView];
//Create custom content
[thisView.layer display];
}
I also implement the following CALayer Delegate method:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
[[NSColor blueColor] setFill];
NSBezierPath *theBez = [NSBezierPath bezierPathWithOvalInRect:layer.bounds];
[theBez fill];
}
If I run this code, I can see the subview being added to the windows contentView (big yellow rectangle), and I'm supposing it is a layer-hosting view... and I can see the oval being drawn in blue, but it is underneath the yellow rectangle, and it's origin is at (0,0) in the main Window... it is like it is not actually being drawn inside the yellow layer.
I'm guessing either my view is not really layer-hosting, or that the context being passed to the layer is wrong... but why would it be underneath?
I must be doing something wrong...
To continue with the weirdness, if I add a CABasicAnimation to the layer, like so:
CABasicAnimation *myAnimation = [CABasicAnimation animation];
myAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
myAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
myAnimation.fromValue = [NSNumber numberWithFloat:0.0];
myAnimation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
myAnimation.duration = 1.0;
myAnimation.repeatCount = HUGE_VALF;
[thisView.layer addAnimation:myAnimation forKey:#"testAnimation"];
thisView.layer.anchorPoint = NSMakePoint(0.5, 0.5);
The yellow background gets animated, rotating about its center, but the blue ellipse gets drawn correctly inside the layer's frame (but also outside, at the origin of the Window, so it is there twice) but does not animate. I would expect the ellipse to rotate with the rest of the layer of course.
I have made this project available here for those willing to give a hand.
Renaud
Got it. I was confused by the fact that the context being called in this situation is a CGContextRef, not an NSGraphicsContext!
I seem to be able to get the result I need by setting the NSGraphicsContext from the CGContextRef:
NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:NO];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:gc];
//Insert drawing code here
[NSGraphicsContext restoreGraphicsState];

How to shadow documentView in NSScrollView?

How to shadow documentView in NSScrollView?
The effect look likes iBook Author:
You need to inset the content in your document view to allow space for the shadow to be displayed, then layer back the view and set a shadow on it. Example:
view.wantsLayer = YES;
NSShadow *shadow = [NSShadow new];
shadow.shadowColor = [NSColor blackColor]
shadow.shadowBlurRadius = 4.f;
shadow.shadowOffset = NSMakeSize(0.f, -5.f);
view.shadow = shadow;
The NSScrollView contentView is an NSView subclass, which has a shadow field, if you create a shadow object and assign it to this field, the view will automatically show a drop shadow when drawn
NSShadow* shadow = [[NSShadow alloc] init];
shadow.shadowBlurRadius = 2; //set how many pixels the shadow has
shadow.shadowOffset = NSMakeSize(2, -2); //the distance from the view the shadow is dropped
shadow.shadowColor = [NSColor blackColor];
self.scrollView.contentView.shadow = shadow;
This works because all views when are drawn on drawRect use this shadow property by using [shadow set].
doing [shadow set] during a draw operation makes whatever is drawn after that to be replicated underneath
I'm new to entering posts on stack overflow but I had the same issue and have solved it so I thought after searching the net for hours to find a solution it would be nice to answer it.
My solution is to create a subclass for NSClipView with the following code for drawRect...
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
NSRect childRect = [[self documentView] frame];
[NSGraphicsContext saveGraphicsState];
// Create the shadow below and to the right of the shape.
NSShadow* theShadow = [[NSShadow alloc] init];
[theShadow setShadowOffset:NSMakeSize(4.0, -4.0)];
[theShadow setShadowBlurRadius:3.0];
// Use a partially transparent color for shapes that overlap.
[theShadow setShadowColor:[[NSColor grayColor]
colorWithAlphaComponent:0.95f]];
[theShadow set];
[[self backgroundColor] setFill];
NSRectFill(childRect);
// Draw your custom content here. Anything you draw
// automatically has the shadow effect applied to it.
[NSGraphicsContext restoreGraphicsState];
}
You then need to create an instance of the subclass and set it with the setContentView selector.
You also need to repaint the clip view every time the content view size changes. If you have your content view set up to change in terms of canvas size when the user wants then unless you repaint the clip view some nasty shadow marks will left behind.
You don't need to mess about with clips as others have suggested.
Hope it helps!

CGGradient isn't visible (not using interface builder) and UIButtons can't be triggered

I have created a view that contains a CGGradient:
// Bar ContextRef
CGRect bar = CGRectMake(0, screenHeight-staffAlignment, screenWidth, barWidth);
CGContextRef barContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(barContext);
CGContextClipToRect(barContext,bar);
// Bar GradientRef
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[16] = { 1.0,1.0,1.0,0.0, 0.0,0.0,0.0,1.0, 0.0,0.0,0.0,1.0, 1.0,1.0,1.0,0.0};
CGFloat locations[4] = {0.95,0.85,0.15,0.05};
size_t count = 4;
CGGradientRef gradientRef = CGGradientCreateWithColorComponents(colorSpace, components, locations, count);
// Draw Bar
CGPoint startPoint = {0.0,0.0};
CGPoint endPoint = {screenWidth,0.0};
CGContextDrawLinearGradient(barContext, gradientRef, startPoint, endPoint, 0);
CGContextRestoreGState(barContext);
This code is called in the drawRect method of the UIView. I then use a UIViewController to access the created view.
- (void)loadView {
MainPageView *mpView = [[MainPageView alloc] initWithFrame:[window bounds]];
[self setView:mpView];
[mpView release];
}
and displayed on the screen through the appDelegate:
mpViewController = [[MainPageViewController alloc] init];
[window addSubview:[mpViewController view]];
[window makeKeyAndVisible];
The UIView contains more objects, such as UIButtons, that are visible. I am assuming because they are added as a subview. But I can't work out how to add the CGGradient as a subview? Does it need to be? Is there another reason CGGradient is not visible?
I also don't get the functionality on the UIButtons. I guess that is because of where I have added the UIButtons to the view. Do the buttons need to be added in the UIViewController or the appDelegate to have functionality. Sorry to ask what would seem like simple questions but I am trying to accomplish the programming without the Interface Builder and material on that is scarce. If anyone could point me in the right direction on both these problems I would really appreciate it.
Thanks!
The functionality on the buttons was lost because the frame was too large but the buttons were still visible because the background was clearColor

Changing alpha of window background, not the whole window

so i have a quick question i have the method below which sets the alpha value of a window depending on the value from a slider, however the content of the window also becomes translucent and eventually disappears with the window.
Is there a way to just change the alpha value of the window and not the content view inside it?
- (IBAction)changeTransparency:(id)sender {
// Set the window's alpha value. This will cause the views in the window to redraw.
[self.window setAlphaValue:[sender floatValue]];}
Thanks, Sami.
Apple's docs gives a way to do this. The key is to set the window's backgroundColor's alpha to the desired value. You must also make sure to set the window's opaque property to NO (which is YES by default.)
e.x.
// At some point in your code...
[window setOpaque:NO];
// Then in your changeTransparency: method...
NSColor *backgroundColor = [window backgroundColor];
backgroundColor = [backgroundColor colorWithAlphaComponent:[sender floatValue]];
[window setBackgroundColor:backgroundColor];
Here is another way.
Suppose,
self.window <--- base view and this alpha will be changed (but exacatly fake).
subView1, subView2 <-- these views are contents of self.window. and theier alpha should not be changed.
self.window.backgroundColor = [UIColor clearColor];
UIView* anAlphaView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.window.frame.size.widht, self.window.frame.size.height)];
anAlphaView.backgroundColor = [UIColor blackColor]; // as you want
anAlphaView.alpha = 0.5f; // as you want.
[self.window addSubview:anAlphaView];
[anAlphaView release];
[self.window addSubview:subView1]; // you should add sub views to self.window
[self.window addSubview:subView2];
You can make a method using above code :)

Resources