Setting mask to CALayer clips sublayer - cocoa

I am working on a Layer-hosting View where I'm setting up a layer hierarchy as:
auto view = [NSView new];
view.layer = [CALayer new];
view.layer.masksToBounds = NO;
view.wantsLayer = YES;
and then using this later as follows:
auto rootLayer = view.layer;
auto layer = [CALayer new];
layer.frame = CGRectMake(0, 0, 100, 100);
layer.backgroundColor = rootLayer.backgroundColor;
[rootLayer addSublayer:layer]; //and similarly adding more sublayers
This works fine and I'm able to see all the layers on the UI. The problem arises when I try to apply a mask to this rootLayer
CAShapeLayer* maskLayer = [CAShapeLayer new];
maskLayer.path = getPath();//sets a CGMutablePathRef
rootLayer.mask = maskLayer;
After this none of the sublayers of rootLayer show up. I've even tried setting the frame of the maskLayer using
rootLayer.mask.frame = CGRectMake(0, 0, rootLayer.frame.size.width, rootLayer.frame.size.height);
but to no avail. I've tried setting the maskToBounds to NO for the rootLayer but it was already NO by default. I can't seem to figure out what's the issue here. Does setting a mask lead all the sublayers to be clipped? I didn't find this documented anywhere. From the documentation of mask:
An optional layer whose alpha channel is used to mask the layer’s
content.
So I interpreted that as to mean that it doesn't impact the sublayers.
I was trying to co-relate this to what's shown here in the Core Animation guide where despite setting the cornerRadius (a mask) to the parent layer, the child layers weren't clipped. But I'm not sure if the same applies when an explicit mask is set.

Related

Drawing correct CALayer colors honoring NSAppearance

I am looking into theming my app by setting window.appearance.
In my app, I draw some stuff inside layers. I also use Core Plot, which renders its charts in layers.
For the default aqua appearance, I just use the system colors (such as NSColor.textColor and NSColor.gridColor) and they are drawn in the correct color in CALayer. But changing the window's appearance to vibrant dark causes colors to be drawn incorrectly.
Is there any way to obtain the correct color for a givenNSAppearance? Private API is acceptable too.
If the question is not clear, here is a very simple example to show the problem.
I set up a CATextLayer that is added as a sublayer of the main view's sublayers and an NSTextFied that is added as a subview:
CATextLayer* textLayer = [CATextLayer new];
textLayer.string = #"Is this colored correctly? (Layer)";
textLayer.foregroundColor = NSColor.textColor.CGColor;
textLayer.contentsScale = 2.0;
textLayer.frame = (CGRect){0,0, [textLayer preferredFrameSize]};
NSTextField* textField = [NSTextField new];
textField.stringValue = #"Is this colored correctly? (View)";
textField.textColor = NSColor.textColor;
textField.font = (__bridge id)textLayer.font;
textField.editable = NO;
textField.selectable = NO;
textField.bezeled = NO;
textField.backgroundColor = nil;
[textField sizeToFit];
textField.frame = (CGRect){0, 60, textField.bounds.size};
[self.view.layer addSublayer:textLayer];
[self.view addSubview:textField];
On an Aqua window, both appear correctly:
However, on a dark vibrant window, the layer does not, while the text field does:
I'd like to know how to get the correct color for a given NSAppearance.
So I had an incorrect approach.
The right way to do it, is to implement -updateLayer in the view and take the colors' CGColor snapshot there. -updateLayer is called when the appearance changes, so the view can update it with the correct color values.

SceneKit - Crossfade material property textures

The documentation for SCNMaterialProperty.contents states that it is an animatable property and indeed I can perform a crossfade between two colors. However I’m unable to crossfade between two images.
So I’m starting to wonder if this is possible at all or if I need to create a custom shader for this?
I’ve tried an implicit animation, in which case it immediately shows the ‘after’ image:
node.geometry.firstMaterial.diffuse.contents = [UIImage imageNamed:#"before"];
[SCNTransaction begin];
[SCNTransaction setAnimationDuration:5];
node.geometry.firstMaterial.diffuse.contents = [UIImage imageNamed:#"after"];
[SCNTransaction commit];
An explicit animation, which does nothing:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"contents"];
animation.fromValue = (__bridge id)[UIImage imageNamed:#"before"].CGImage;
animation.toValue = (__bridge id)[UIImage imageNamed:#"after"].CGImage;
animation.duration = 5;
[node.geometry.firstMaterial.diffuse addAnimation:animation forKey:nil];
As well as through a CALayer, which does nothing:
CALayer *textureLayer = [CALayer layer];
textureLayer.frame = CGRectMake(0, 0, 793, 1006);
textureLayer.contents = (__bridge id)[UIImage imageNamed:#"before"].CGImage;
node.geometry.firstMaterial.diffuse.contents = textureLayer;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"contents"];
animation.fromValue = (__bridge id)[UIImage imageNamed:#"before"].CGImage;
animation.toValue = (__bridge id)[UIImage imageNamed:#"after"].CGImage;
animation.duration = 5;
[textureLayer addAnimation:animation forKey:nil];
From my own testing, it doesn't look like this property is actually animatable when texture values are involved (rather than solid color values). Either that's a bug in SceneKit (i.e. it's intended to be animatable but that's not working) or it's a bug in Apple's docs (i.e. it's not intended to be animatable but they say it is). Either way, you should file that bug so you get notified when Apple fixes it.
(It also doesn't look like this is a tvOS-specific issue -- I see it on OS X as well.)
I can understand why animated texture transitions might not be there... from a GL/Metal perspective, that requires binding an extra texture unit and having two texture lookups per pixel (instead of one) during the transition.
I can think of a couple of decent potential workarounds:
Use a shader modifier. Write a GLSL(ish) snippet that looks something like this:
uniform sampler2D otherTexture;
uniform float fadeFactor;
#pragma body
vec4 otherTexel = texture2D(otherTexture, _surface.diffuseTexcoord);
_surface.diffuse = mix(_surface.diffuse, otherTexel, fadeFactor);
Set it on the material you want to animate using the SCNShaderModifierEntryPointSurface entry point. Then use setValue:forKey: to associate a SCNMaterialProperty with the otherTexture and a CABasicAnimation to animate the fadeFactor from 0 to 1.
Use something more animated (like a SpriteKit scene) as your material property contents, and animate that to perform the transition. (As a bonus, when you do it this way, you can use other transition styles.)
Your animation isn't happening because "contents" property is animatable only when set to a color not for image. You can read it on the apple's documentation about contents property.

How to animate a CALayer to have empty contents

I have a CALayer with an image in it, and it has several sublayers. I want to animate it to have no contents (no image), but continue showing the sublayers. This code does not work:
CABasicAnimation *backgroundOut = [CABasicAnimation animationWithKeyPath:#"contents"];
backgroundOut.toValue = [NSNull null];
backgroundOut.fillMode = kCAFillModeForwards;
backgroundOut.removedOnCompletion = NO;
backgroundOut.duration = 3.0;
[_backgroundLayer addAnimation:backgroundOut forKey:#"contents"];
Here is the only way I could get this to work:
backgroundOut.toValue = (__bridge id)([UIImage imageNamed:#"blankImage"].CGImage);
Note that I don't want to mess with the opacity or anything because this layer has sublayers that need to still be visible.
What is the proper way to animate to empty contents?
I decided this was the cleanest approach, since there was no other answer forthcoming:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0);
UIImage *blank = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CABasicAnimation *backgroundOut = [CABasicAnimation animationWithKeyPath:#"contents"];
backgroundOut.toValue = (__bridge id)(blank.CGImage);
backgroundOut.fillMode = kCAFillModeForwards;
backgroundOut.removedOnCompletion = NO;
backgroundOut.duration = 3.0;
[_backgroundLayer addAnimation:backgroundOut forKey:#"contents"];
Another approach might be to create a separate layer to contain your image that is a peer of your other layers, but just behind the other layers (you can use zposition to place it behind the other layers explicitly)--all contained within a root layer. Then, you can animate the image layer's alpha for a fade and won't have the overhead of creating an image on the fly and it won't then fade the other layers.

subview of layer backed nsview clipped

I have a subview of an layer backed nsview (set through storyboard) which exceed the bounds. For some reason it is getting clipped. Any idea why this is happening ?
By default NSView will clip its subviews to its bounds.
If you have a layer backed NSView, alignmentRectInsets: might provide your solution. It returns an NSEdgeInsets that adds a margin to the layer's clipping bounds. Override the read-only property in your subview and return the desired insets.
If you need a more sophisticated way to inset the clipping, take a look at alignmentRectForFrame: and frameForAlignmentRect:.
See: developer.apple.com/…/Cocoa/Reference/ApplicationKit/Classes/NSView_Class
I just solved the problem.
It wasted me some time although the method is very simple.
There are two things need to pay attention to.
First of all (THE MOST IMPORTANT!!),you should set your winow's contentview.wantsLayer YES;
And then , set the parent view's layer.maskesToBounds NO;
What I did:
self.window.contentView.wantsLayer = YES;
self.testview.layer.backgroundColor = [NSColor clearColor].CGColor;
self.testview.layer.borderWidth = 3;
self.testview.layer.borderColor = [NSColor blueColor].CGColor;
self.testview.layer.masksToBounds = NO;
NSView *aView = [[NSView alloc]initWithFrame:NSMakeRect(-50, -20, 100, 100)];
aView.wantsLayer = YES;
aView.layer.backgroundColor = [NSColor redColor].CGColor;
[self.testview addSubview:aView];
The effect
GOOD LUCK

navigation bar background did not change

i have a navigation bar, i want to change it using image, i was implement this code
self.navBar.layer.contents = (id)[UIImage imageNamed:#"flower.png"].CGImage;
but the background is still didn't change. the hierarchy of navigation bar are :
- UINavigationBar
- UINavigationItem
- UISegementControl
i already set an outlet in every part in that hierarchy..
is there something wrong???
Here's what I've used quite succesfully:
CALayer *backgroundImageLayer;
backgroundImageLayer = [CALayer layer];
backgroundImageLayer.frame = CGRectMake(0, 0, 300, 44);
backgroundImageLayer.backgroundColor = [UIColor redColor].CGColor;
backgroundImageLayer.contents = (id)[[UIImage imageNamed:#"barImage.png"] CGImage];
backgroundImageLayer.zPosition = -5.0;
[myNavigationConroller.navigationBar.layer addSublayer:test];
The key was setting the zPosition to -5.0, although any negative value should work. It causes the backgroundImageLayer not to interfere with UINavigationBar's buttons and label.

Resources