How to properly set up CALayer hierarchy? - cocoa

I'm trying to arrange views one above another, so that front view would appear with some animation. It looks like the only way to achieve that is to use CALayer. Here is code that I use
-(void)viewDidLoad {
CALayer *frontLayer = [CALayer layer];
CALayer *rearLayer=[CALayer layer];
frontLayer.backgroundColor=CGColorCreateGenericRGB( 0.25, 0.45, 0.35, 1.0 );
frontLayer.frame = CGRectMake(0,311, 266,21);
[_outputText setLayer:rearLayer];
[_outputText setWantsLayer:YES];
[_outPutTextHideBox setLayer:frontLayer];
[rearLayer addSublayer:frontLayer];
}
Here _outputText is NSTextView and _outPutTextHideBox is NSBox.
The problem is that my frontLayer is not displayed on the top of rearLayer. Actually, frontLayer is displayed under rearLayer.

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.

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.

Can you set the anchorPoint of the root layer on an NSView?

Is it possible to modify the anchorPoint property on the root CALayer of a layer-backed NSView?
I have a view called myView and it seems every time I set the anchorPoint, it gets overridden in the next run loop. I am doing this:
NSView *myView = [[myView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
//set the root layer
myView.layer = [CALayer layer];
myView.wantsLayer = YES;
//gets overridden on the next run loop
myView.layer.anchorPoint = CGPointMake(1,1);
On 10.8, AppKit will control the following properties on a CALayer
(both when "layer-hosted" or "layer-backed"): geometryFlipped, bounds,
frame (implied), position, anchorPoint, transform, shadow*, hidden,
filters, and compositingFilter. … Use the appropriate NSView cover
methods to change these properties.
Basically it will set the anchor to [0,0] from [0.5,0.5], to account for this i uses something like :
+(void) accountForLowerLeftAnchor:(CALayer*)layer
{
CGRect frame = layer.frame;
CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
layer.position = center;
layer.anchorPoint = CGPointMake(0.5, 0.5);
}

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!

CALayer, anchorPoint and layer hierarchy

I try to use the anchorPoint in a layer hierarchy to move a layer with its sublayers. Unfortunately the sublayer did not move together with the root layer. In a custom NSView is set up my layer hierarchy like in the following snippet.
CALayer * rootLayer;
rootLayer = [[CALayer layer] retain];
rootLayer.position = CGPointMake(...);
[self.layer addSublayer:rootLayer];
subLayer = [[MapLayer layer] retain];
subLayer.position = CGPointMake(...);
[rootLayer addSublayer:baseLayer];
While handling a mouse event I want to set the anchorPoint of the rootLayer to move the whole layer hierarchy:
rootLayer.anchorPoint = CGPoint(0.7, 0.7);
I expect from this call, that the rootLayer moves together with its subLayer so that the anchor point is at the center of the view. What happens is, that the sublayer did not move. Only when I call:
rootLayer.anchorPoint = CGPoint(0.7, 0.7);
subLayer.anchorPoint = CGPoint(0.7, 0.7);
the layers behave as expected.
I thought setting the anchor point of the root layer would be enough. This is used in my Map application for OS X. There I set up the view and use the anchorPoint to move the whole map. With only one sublayer in the custom NSView the application behaves as expected.
Thanks in advance.
UPDATE: After a discussion with a colleague I set the bounds of the rootLayer and now it works.
CALayer * rootLayer;
rootLayer = [[CALayer layer] retain];
rootLayer.bounds = CGRectMake(0, 0, 256, 256);
rootLayer.position = CGPointMake(...);
[self.layer addSublayer:rootLayer];
// ...

Resources