Consider the following...
Say I have two CALayer's, one on top of the other. Each layer is the size of the entire iPad screen, the top layer obscures the bottom layer.
Is there some way to mark a portion of the top layer as being "transparent", so that the same section of the bottom layer shows through the transparent portion? In other words, is there a way to "cut out" a portion of the top layer to reveal the bottom layer underneath?
The CALayer mask property. You'll need to subclass CALayer to drawToContext: opaque black over the entire bounds, then do a CGContextClear(ctx, <your see-through box>);
Then create an instance of the layer, give it the same frame as your top-layer bounds, and set it to the mask property.
Yes, you can do it by making different alpha values for each layer,
basically the inner layer(super) one should have at least an alpha value of 0.7 and the outer layer(subLayer) should have less alpha value than its parent, lets say 0.3
Then the outer layer should reveal the inner layer.
But if you wanna do some better revelation, you could draw the outer layer, by setting radial gradient on it.
This is my sample code, but it I haven't drawn the radial gradient for the outer layer.
//
CALayer *innnerLayer = [CALayer layer];
innnerLayer.borderColor = [UIColor greenColor].CGColor;
innnerLayer.borderWidth = 0.8f;
innnerLayer.backgroundColor = [UIColor colorWithWhite:0. alpha:0.5].CGColor;
innnerLayer.frame = CGRectMake(70.0, 150.0f, 100.0f, 100.0f);
CALayer *outLayer = [CALayer layer];
outLayer.frame = innnerLayer.bounds;
outLayer.backgroundColor = [UIColor colorWithWhite:0.f alpha:0.3f].CGColor;
// add outer layer to inner layer
[innnerLayer addSublayer:outLayer];
// add the inner layer to main view
[self.view.layer addSublayer:innnerLayer];
// Experiment with different alpha values, but the outerAlpha
Are you using CALayer as a sublayer of your UIView? You have to set the backgroundColor of your UIView to clear like this:
self.backgroundColor = [UIColor clearColor];
Just setting backgroundColor property to NULL helped me.
Related
I want to add a drop shadow effect underneath my UIImageView instances, like in the screenshot below:
How can I achieve this, either through storyboards or code (Objective-C)?
//Drop Shadow
[view.layer setShadowColor: [UIColor grayColor].CGColor];
[view.layer setShadowOpacity:0.8];
[view.layer setShadowRadius:3.0];
[view.layer setShadowOffset:CGSizeMake(2.0, 2.0)];
Please Try this once :-
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;
Explanation :-
As seen in the last step there are a few different properties needed when adding a shadow using Core Animation. If your are not familiar with Core Animation don’t worry, just try to focus on the shadow code and not the CALayer on the view.
shadowOffset is a CGSize representing how far to offset the shadow from the path.
shadowColor is the color of the shadow. Shadow colors should always be opaque, because the opacity will be set by the shadowOpacity property. The shadowColor property is a CGColor not a UIColor.
shadowRadius is the width of the shadow along the shadow path
shadowOpacity determines the opacity of the shadow.
shadowPath is probably the most important of the properties. While a shadow can be drawn without specifying a path, for performance reasons you should always specify one. This path tells Core Animation what the opaque regions of the view are, and without it, things slow down severely! It is a CGPath, which is most easily created using UIBezierPath (iOS only) as shown in step 2.
See more in http://nscookbook.com/2013/01/ios-programming-recipe-10-adding-a-shadow-to-uiview/
Those are not lights, but shadow. You can use the layer to draw shadow for UI elements.
I have UIImageView with added mask to it's layer. And after that I want to add border to the image. But, looks like mask applying to the border also, because it's changing color (should be strong white, w\o mask).
My code for doing this looking as
// Mask
CALayer *layer = [CALayer layer];
layer.frame = _photo.bounds;
layer.backgroundColor = [UIColor colorWithWhite:0.f alpha:.5f].CGColor;
[_photo.layer setMask:layer];
_photo.layer.cornerRadius = _photo.frame.size.width / 2;
_photo.layer.masksToBounds = YES;
_photo.clipsToBounds = YES;
// Border
_photo.layer.borderColor = [[UIColor whiteColor] CGColor];
_photo.layer.borderWidth = 2.0;
Image w\o mask applied:
Result is next:
How I can add border without changing it's color during mask apply?
I suspect you can't (easily) stop the layer's mask from also masking the layer's border. That being the case, I'd suggest creating a second view or layer just for the border, in front of _photo but outside its view hierarchy. You can then give that second layer the same bounds, corner radius and border as _photo's layer without the mask applying to it.
I have a UIView, with view.layer.mask set to an instance of CAShapeLayer. The shape layer contains a path, and now I want to add a hole to this shape by adding a second shape with even/odd rule, and fade-in the appearance of this hole.
The problem is that adding to path doesn't seem to be animatable:
[UIView animateWithDuration:2 animations:^() {
CGPathAddPath(parentPath, nil, holePath);
[v.layer.mask didChangeValueForKey:#"path"];
}];
How would I animate this?
After some fiddling, found a workaround:
Create a layer with two sublayers with two desired shapes, and use it as a mask
Animate opacity of the first sublayer (without a hole) from 1 to 0.
This works because child CAShapeLayer instances appear to be used as a union. When you hide the first sublayer without a hole, only the hole will be uncovered, the shared area will not change.
CGMutablePathRef p = CGPathCreateMutable();
// First path
CGPathAddPath(p, nil, outerPath);
CAShapeLayer *mask1 = [CAShapeLayer layer];
mask1.path = p;
// 2nd path with a hole
CGPathAddPath(p, nil, innerPath);
CAShapeLayer *mask2 = [CAShapeLayer layer];
mask2.path = p;
mask2.fillRule = kCAFillRuleEvenOdd;
CGPathRelease(p);
// Create actual mask that hosts two submasks
CALayer *mask = [CALayer layer];
[mask addSublayer:mask1];
[mask addSublayer:mask2];
myView.layer.mask = mask;
mask.frame = v.layer.bounds;
mask1.frame = mask.bounds;
mask2.frame = mask.bounds;
// ...
// Hide mask #1
CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:#"opacity"];
a.fromValue = #1.0;
a.toValue = #0.0;
a.duration = 1.0;
a.fillMode = kCAFillModeForwards; // Don't reset back to original state
a.removedOnCompletion = NO;
[mask1 addAnimation:a forKey:#"hideMask1"];
You can't use UIView animation to animate CALayers.
Most layer property changes do animation by default (implicit animation). As I recall, shape layer path changes are an exception to that.
You'll need to create a CAAnimation object where the property you are animating is the path on your mask layer.
However, that probably won't give the effect you want. The reason is that when you change a path on a shape layer, Core Animation tries to animate the change in shape of the path. Furthermore, path changes only work properly when the starting and ending paths have the same number and type of control points.
I'm not sure how you'd achieve a cross-fade between 2 different masks without a lot of work.
Off the top of my head, the only way I can think of to do this would be to create a snapshot of the new view appearance with the changed mask (probably using Core Image filters) and then do a cross-fade of a layer that displays that snapshot. Once the crossfade is complete, you would install the new path in your mask layer without animation and then remove the snapshot bitmap, revealing the real view underneath.
There might be a simpler way to achieve what you're after but I don't know what that would be. Maybe one of the CA experts that contributes to SO could chime in here.
I have a UIView that programmatically draws a "sunburst" pattern using UIBezierPath. Now I would like to extend this by masking the edges with a gradient -- effectively making each "burst" fade from opaque to transparent. I'm think this could be done with a CAGradientLayer mask but I'm not sure how to make it circular.
This is what I'm trying -- it masks the view but the gradient is linear:
CAGradientLayer *l = [CAGradientLayer layer];
l.frame = CGRectMake(0.0f, 0.0f, rect.size.width, rect.size.height);
l.cornerRadius = rect.size.width/2.0f;
l.masksToBounds = YES;
l.colors = [NSArray arrayWithObjects:(id)[UIColor blackColor].CGColor, (id)[UIColor clearColor].CGColor, nil];
self.layer.mask = l;
I'm open to not using CAGradientLayer if anyone knows any other ways to mask a view with a circle with blurred edges.
SOLUTION
Thanks to matt's insight, I ended up drawing a mask view using CGContextDrawRadialGradient, rendering that as a UIImage, and using that as a mask layer. If anyone is interested in this process, it is being used in this test project.
An obvious approach is to draw this effect manually. Start very small and draw the sunburst larger and larger and (at the same time) less and less opaque.
On the other hand, it might be sufficient to make a radial gradient and use it as a mask (vignette effect). Core Graphics will draw the radial gradient for you:
https://developer.apple.com/library/ios/#documentation/graphicsimaging/reference/CGContext/Reference/reference.html#//apple_ref/c/func/CGContextDrawRadialGradient
I'm also very fond of CIFilters for adding touches like this: you should look through the catalogue and see what strikes your fancy:
https://developer.apple.com/library/ios/#documentation/graphicsimaging/reference/CoreImageFilterReference/Reference/reference.html
CIFilter actually gives you a sunburst transition that might suit your purposes, especially if combined with masking and compositing; here's a discussion of the sunburst (used in an animation), from my book:
http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions
CAGradientLayer draws linear gradients.
You could try to set a shadow and see if that suits your needs:
l.shadowColor = [UIColor blackColor];
l.shadowRadius = rect.size.width / 2;
You can do this by creating a CALayer instance and give it a delegate that implements drawLayer:inContext: and draws a radial gradient in the implementation of that method. Then use that CALayer as your mask.
I have a view with a border of 10 pixels drawn on the method.
I need to update the border color and I use [self setNeedsDisplay] to make it redraw
the view.
Since I need to update only the border I want to use : [self setNeedsDisplayInRect:rect] so it will draw only the border.
How can I get a rect of only the border with out the other areas of the view?
Thanks
Shani
You can't because a CGRect is rectangle, so it is a convex shape that can't have holes in it.
But you can decompose the border into four rectangles and call [self setNeedsDisplayInRect:rect] four times.
Also, if you import QuartzCore, you can probably use the property borderColor of the view's layer:
#import <QuartzCore/QuartzCore.h>
// ...
view.layer.borderWidth = 10;
view.layer.borderColor = [UIColor redColor].CGColor;
// And to change it later
view.layer.borderColor = [UIColor greenColor].CGColor;
You could get four CGRects around each part of the border (top, right, bottom, and left) and call the method four times with each of them.