NSView layer shadow draws on subviews, not view itself - cocoa

I'm trying to set a shadow on the layer of an NSView, but the layer is being drawn on the subviews of my view, not the view itself. What can be causing this?
The view in question and all its subviews are layer-backed. All but one of the subviews are buttons (NSButton) with images - the other one is a custom view that renders via drawRect:.
self.layer.shadowColor = [[NSColor blackColor] colorWithAlphaComponent:0.9].CGColor;
self.layer.shadowRadius = 2;
self.layer.shadowOffset = NSMakeSize(0, -1);
self.layer.shadowOpacity = 0.9;
I've also tried setting the shadow via an NSShadow, with the same outcome.

Turns out that if you set a shadow on an NSView's layer, it only draws on that view if the view has a drawRect: implementation that draws something non-transparent. Otherwise it draws on all the view's subviews.
Weird.

Related

Sublayer is not visible unless added after viewDidAppear

I am trying to load a profile image for an app, but I want to add a white border to it and make it circular before I display it. In order to do that I set the image property on the UIImageView and then prepare the image by setting the appropriate corner radius, masking it to the bounds and then adding a white border sublayer. Originally, I was loading the image in over the internet asynchronously and everything worked fine because the image appeared after the view had appeared. However, when I cached the image and tried to prepare it before the imageView was shown on screen I couldn't get the border sublayer to display. It still masked properly, just no border. The code that I am using to prepare the image is below.
- (void)prepareProfileImage
{
CALayer *imageLayer = self.profileImageView.layer;
imageLayer.borderColor = [UIColor whiteColor].CGColor;
imageLayer.allowsEdgeAntialiasing = NO;
[imageLayer setCornerRadius:PROFILE_IMAGE_DIAMETER/2.0];
[imageLayer setMasksToBounds:YES];
CALayer *borderLayer = [CALayer layer];
CGRect borderFrame = CGRectMake(-1.0, -1.0, (self.profileImageView.frame.size.width+2.0), (self.profileImageView.frame.size.height+2.0));
[borderLayer setBackgroundColor:[[UIColor whiteColor] CGColor]];
[borderLayer setFrame:borderFrame];
[borderLayer setCornerRadius:PROFILE_IMAGE_DIAMETER/2.0];
[borderLayer setBorderWidth:PROFILE_IMAGE_BORDER_WIDTH+1.0];
[borderLayer setBorderColor:[UIColor whiteColor].CGColor];
[imageLayer addSublayer:borderLayer];
}
This method works fine as long as it's called after viewDidLoad, but will not add the border layer if called before viewDidLoad. I have confirmed that self.profileImageView has been allocated at the time of this method call, but I only get the circular image, no border.
Is there something that I am misunderstanding about CALayers? Should it matter when I add the layer as a sublayer?
The reason I am not using the border property directly on the image layer is that it leaves a tiny sliver of image around the outside that is displeasing.
I have found the answer! The problem was that I want setting the frame of the border layer using the profileImageView frame, but because I am using autolayout that property is not set until after the view is displayed on screen.
I ended up doing:
/*! This function adds a border layer to the profile image view. */
- (void)addBorderLayerToProfileImageView {
CALayer *borderLayer = [CALayer layer];
CGRect borderFrame = CGRectMake(-1.0, -1.0, (PROFILE_IMAGE_DIAMETER+2.0), (PROFILE_IMAGE_DIAMETER+2.0));
[borderLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[borderLayer setFrame:borderFrame];
[borderLayer setCornerRadius:PROFILE_IMAGE_DIAMETER/2.0];
[borderLayer setBorderWidth:PROFILE_IMAGE_BORDER_WIDTH+1.0];
[borderLayer setBorderColor:[UIColor whiteColor].CGColor];
[self.profileImageView.layer addSublayer:borderLayer];
}
The reason I was adding a border layer at all is because the border on the layer leaves a small artifact between the edge of the image view and the border.
I was having the same issue as you: sublayer not being visible unless I called it on viewDidAppear.
I saw your answer but it did not work for me. I am using AutoLayout so I needed a solution that would be dynamic, not knowing my height or width of my view. My frame.size of my view I was trying to add a layer to would has a height and width value of 0 until viewDidAppear (makes sense).
After learning more about the lifecycle of UIView and UIViewController, the solution for me was to use the layoutSubviews() function of my custom UIView to add the subview to it as AutoLayout has measured the views at that point.
You can also add layers to subviews in a UIViewController in the viewDidLayoutSubviews() function as AutoLayout has measured by this point as well.

NSTableView with rounded corners

I'm trying to customize the UI of my application and I want my NSTableView to have rounded corners. So I subclassed NSTableView and got this:
However, when I populate the table and select a row, the selection is drawn over the border like this:
I've tried adding a clip in the table view drawing code and it doesn't work. Any suggestions for how I can fix this?
Edit:
My drawing code in the NSTableView is the following:
- (void)drawRect:(NSRect)dirtyRect {
[NSGraphicsContext saveGraphicsState];
NSRect frame = NSMakeRect(0.0, 0.0, [self bounds].size.width, [self bounds].size.height-1.0);
[[NSBezierPath bezierPathWithRoundedRect:frame xRadius:3.6 yRadius:3.6] addClip];
[super drawRect:dirtyRect];
[NSGraphicsContext restoreGraphicsState];
}
The actual rounded frame is drawn in the NSScrollView drawRect method. The interesting thing is that this does clip the selection of the very first and very last rows:
But not when the table is scrolling:
So the question remains: how can I clip all drawing inside the rounded frame of the NSScrollView?
I found that you can call this on the container scroll view of the table view.
self.scrollView.wantsLayer = TRUE;
self.scrollView.layer.cornerRadius = 6;
That's all I needed and it works. No subclassing needed.
I was able to solve this pretty nicely using CALayer. After trying subclassing everything from NSScrollView to NSTableView to NSClipView, and still getting the rendering problems shown above, I finally simply added this code to the drawRect of the NSScrollView subclass:
if (!self.contentView.wantsLayer) {
[self.contentView setWantsLayer:YES];
[self.contentView.layer setCornerRadius:4.0f];
}
And then I draw the frame in the same drawRect method of the NSScrollView. It solves all the problems above.

cocoa -- getting nested NSViews and CALayers to resize proportionally

My NSWindow's contentView is an NSView subclass. It has some other NSView subclasses as subviews. The subviews are layer-based, and those layers in turn contain sublayers. Some of the sublayers have further sub-sublayers.
I want the whole thing to resize proportionally when the window is resized. What is the right way to set it up so that will happen?
Thanks
EDIT: I am not using Interface Builder at all.
Here's what I've done to get the contents of an NSView to scale proportionally as I resize the parent window. First, in interface builder, I added my NSView to the window, then added a reference to it in my AppDelegate. Mine happens to be called scrollView. I removed all of the auto-sizing behaviour from the scrollView.
Then, in my AppDelegate I added this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// keep the aspect ratio constant so that the content looks good
[window setContentAspectRatio:NSMakeSize(2, 1)];
window.delegate = self;
}
- (void)windowDidResize:(NSNotification *)notification {
// size the scrollView to fill the window, but keep its bounds constant
NSRect rect = [[window contentView] frame];
NSRect oldBounds = [scrollView bounds];
[scrollView setFrame:rect];
[scrollView setBounds:oldBounds];
}
This turns the AppDelegate into the window delegate too. Fine since I've not got much logic in it. By keeping the bounds constant while changing the frame, the contents of scrollView will be scaled down smoothly.

CATransition replaceSubView:with…is animating all views

how can I make it so all of my view's subviews aren't animated here and only the currentPage's representedView and the newPage's represented view's?
CATransition *transition = [CATransition animation];
[transition setType:kCATransitionPush];
[transition setSubtype:([self indexOfPage:currentPage] < [self indexOfPage:newPage]) ? kCATransitionFromRight : kCATransitionFromLeft];
NSDictionary *ani = [NSDictionary dictionaryWithObject:transition
forKey:#"subviews"];
[self setAnimations:ani];
[self.animator replaceSubview:currentPage.representedView with:newPage.representedView];
First, it seems odd that you are requesting to not animate subviews when this is the exact property you created your animation for so I'm not real clear on what you're trying to do.
Second, the default behavior for animation it to animate the view and all of its subviews. If you want to only animate the parent view and specific subviews, you should not add the views you don't want to animate as children of it, but rather make them siblings with the proper z order.

Core Animation with an NSView and subviews

I've subclassed NSView to create a 'container' view (which I've called TRTransitionView) which is being used to house two subviews. At the click of a button, I want to transition one subview out of the parent view and transition the other in, using the Core Animation transition type: kCATransitionPush. For the most part, I have this working as you'd expect (here's a basic test project I threw together).
The issue I'm seeing relates to resizing my window and then toggling between my two views. After resizing a window, my subviews will appear at seemingly random locations within my TRTransitionView. Additionally, it appears as if the TRTransitionView hasn't stretched correctly and is clipping the contents of its subviews. Ideally, I would like subviews anchored to the top-left of their parent view at all times, and to also grow to expand the size of the parent view.
The second issue relates to an NSTableView I've placed in my first subview. When my window is resized, and my TRTransitionView resizes to match its new dimensions, my TableView seems to resize its content quite awkwardly (the entire table seems to jolt around) and the newly expanded space that the table now occupies seems to 'flash' (as if in the process of being animated). Extremely difficult to describe, but is there any way to stop this?
Here's my TRTransitionView class:
-(void) awakeFromNib
{
[self setWantsLayer:YES];
[self addSubview:[self currentView]];
transition = [CATransition animation];
[transition setType:kCATransitionPush];
[transition setSubtype:kCATransitionFromLeft];
[self setAnimations: [NSDictionary dictionaryWithObject:transition forKey:#"subviews"]];
}
- (void)setCurrentView:(NSView*)newView
{
if (!currentView) {
currentView = newView;
return;
}
[[self animator] replaceSubview:currentView with:newView];
currentView = newView;
}
-(IBAction) switchToViewOne:(id)sender
{
[transition setSubtype:kCATransitionFromLeft];
[self setCurrentView:viewOne];
}
-(IBAction) switchToViewTwo:(id)sender
{
[transition setSubtype:kCATransitionFromRight];
[self setCurrentView:viewTwo];
}

Resources