Masking a sublayer gradient to CAShapeLayer - objective-c

I have this CAShapeLayer with a bezierPathWithOvalInRect. I want to make the gradient layer fill only the circle, but at the moment it fills the whole frame.
How do I do this?
CAShapeLayer *layerInsides = [CAShapeLayer layer];
layerInsides.path = [UIBezierPath bezierPathWithOvalInRect:rectFrom(frameInsides)].CGPath;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = layerInsides.frame;
gradientLayer.colors = #[(id)[UIColor colorWithRed:1 green:0 blue:0 alpha:1].CGColor, (id)[UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor];
gradientLayer.locations = #[#0.0, #1];
gradientLayer.cornerRadius = self.layer.cornerRadius;
[layerInsides addSublayer:gradientLayer];

Instead of adding your content to the shape as a sublayer you need to set your CAShapeLayer as a mask for your colored layer:
gradientLayer.mask = layerInsides;

Related

iOS 10 How to animate UIBezierPath of UIVisualEffectView maskView

I just updated to Xcode 8 and I had problem with Blur (UIVisualEffectView) on iOS 10, but I solve it. But after I fixed the blur layer mask second problem is appears, when I getting the reference of maskView from UIVisualEffectView and apply the new UIBezierPath nothing happens.
Code example:
-(void)prepareEffectView{
//Frame
CGRect frame = self.view.bounds;
UIView *maskView = [[UIView alloc] initWithFrame:frame];
maskView.backgroundColor = [UIColor blackColor];
maskView.layer.mask = ({ // This mask draws a rectangle and crops a circle inside it.
UIBezierPath *maskLayerPath = [UIBezierPath bezierPath];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:frame]];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(self.btnScan.center.x, self.btnScan.center.y, 0, 0)]];
[maskLayer setPath:maskLayerPath.CGPath];
[maskLayerPath setUsesEvenOddFillRule:YES];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = maskLayerPath.CGPath;
mask.fillRule = kCAFillRuleEvenOdd;
mask;
});
self.visualEffectView.maskView = maskView;
}
-(void)openAperture{
//Blur layer
CGRect frame = self.view.bounds;
CAShapeLayer *maskLayer = (CAShapeLayer *)self.visualEffectView.maskView.layer.mask;
//New path value
UIBezierPath *maskLayerPath = [UIBezierPath bezierPath];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:frame]];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRoundedRect:UIEdgeInsetsInsetRect(frame, UIEdgeInsetsMake(58, 15, 66, 15)) cornerRadius:1]];
[maskLayer setPath:maskLayerPath.CGPath];
//Animation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.delegate = self;
animation.duration = 0.44;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (id)maskLayer.path;
animation.toValue = (id)maskLayerPath.CGPath;
[maskLayer addAnimation:animation forKey:#"animatePath"];
}
Apple Note About VisualEffectView issue on iOS 10
Transitions are tough in this realm, as maskView is the only way to
ensure that the effect works, but maskView also isn't animatable.
https://forums.developer.apple.com/message/184810#184810
The only the way to use "CAShapeLayer" as a mask is to apply your shape to UIView.layer.mask and then put it to UIVisualEffectView.maskView.
The maskView of UIVisualEffectView isn't animatable.
I have the same problem.
I tried to do next: (Swift code)
visualEffectView.layer.mask = maskView.layer.mask
instead of visualEffectView.mask = maskView
If so, path animation works fine, but then blur effect is gone.
If you find a solution, please update the post.

Adding animated layer on video using CAKeyframeAnimation

I'm trying to add an overlay to my video which contains a sublayer that has the content changed over time. So I'm adding CAKeyframeAnimation to the layer as follows:
CALayer *overlayLayer = [CALayer layer];
[overlayLayer setFrame:CGRectMake(0, 0, size.width, size.height)];
[overlayLayer setMasksToBounds:YES];
NSArray *frames = [self.gifImage frames];
CALayer *aLayer = [CALayer layer];
UIImage *firstImage = [frames objectAtIndex:0];
CGFloat nativeWidth = CGImageGetWidth(firstImage.CGImage);
CGFloat nativeHeight = CGImageGetHeight(firstImage.CGImage);
CGRect frame = CGRectMake(0.0, 0.0, nativeWidth, nativeHeight);
aLayer.frame = frame;
aLayer.contents = (id)firstImage.CGImage;
[aLayer setMasksToBounds:YES];
CAKeyframeAnimation *animationSequence = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animationSequence.calculationMode = kCAAnimationDiscrete;
animationSequence.duration = [self.gifImage totalDuration];
animationSequence.repeatCount = HUGE_VALF;
NSMutableArray *animationSequenceArray = [[NSMutableArray alloc] init];
for (UIImage *image in frames) {
[animationSequenceArray addObject:(id)image.CGImage];
}
animationSequence.values = animationSequenceArray;
[aLayer addAnimation:animationSequence forKey:#"contents"];
[overlayLayer addSublayer:aLayer];
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, size.width, size.height);
videoLayer.frame = CGRectMake(0, 0, size.width, size.height);
[parentLayer addSublayer:videoLayer];
[parentLayer addSublayer:overlayLayer];
composition.animationTool = [AVVideoCompositionCoreAnimationTool
videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
Somehow when I add aLayer as a sublayer of my view, it animates properly. But When I add it to the overlay of the video, the animation doesn't work. Is there something I'm missing here? Any help is appreciated.
If you use kCAAnimationDiscrete, here's a checklist:
keyTimes needs a value from 0-1, not a time
keyTimes must start at 0.0 and end with value 1.0
keyTimes must have one more value than your animation frames count
I've finally managed to fix this issue by changing calculationMode to kCAAnimationLinear. This seems to be a silly issue, but I hope it may be useful for someone.

Gradient as circle stroke color in CAShapeLayer

CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(x, y, 100, 100) cornerRadius:50].CGPath;
// Configure the apperence of the circle
circle.fillColor = [UIColor whiteColor].CGColor;
circle.strokeColor = [UIColor darkGrayColor].CGColor;
circle.lineWidth = 1.8;
circle.lineDashPattern = #[#6, #3];
// Add to parent layer
[cell.layer addSublayer:circle];
I am using this code to draw a circle. Now, I want to use a gradient as stroke color. How can I do that?

UIView rounded corners

I have to round only upper corners of UIView. I'm trying to use the following code:
UIBezierPath *maskEmailPath = [UIBezierPath bezierPathWithRoundedRect:self.emailandAccessView.bounds byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskEmailLayer = [CAShapeLayer layer];
maskEmailLayer.frame = self.myview.bounds;
maskEmailLayer.path = maskEmailPath.CGPath;
self.myview.layer.mask = maskEmailLayer;
but it hides everything in that view. Can anyone please help me.
This is how you do it.
CALayer *capa = self.view.layer;
CGRect bounds = capa.bounds;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:bounds
byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight)
cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = bounds;
maskLayer.path = maskPath.CGPath;
[capa addSublayer:maskLayer];
capa.mask = maskLayer;

Shadow on sublayer of CAGradientLayer

I have a CALayer as a sublayer to a CAGradientLayer and I find that shadows do not render. I'm wondering if someone has done this successfully?
Heres my implementation:
CAGradientLayer *root;
// set up root with gradient
// gradient displays well
[self.layer addSublayer:root];
CALayer *menuBox = [CALayer layer];
menuBox.backgroundColor = [UIColor whiteColor].CGColor;
menuBox.frame = CGRectInset(root.frame, 20, 20);
menuBox.cornerRadius = 15.0;
menuBox.masksToBounds = YES;
menuBox.shadowColor = [UIColor greenColor].CGColor;
menuBox.shadowOffset = CGSizeMake(4.0,6.0);
menuBox.shadowOpacity = 1.0;
menuBox.shadowRadius = 4.0;
//add sublayers to root in correct order, bottom first
[root addSublayer:menuBox];
I display a rounded white rectangle over a gradient background. I want the white rectangle to have a shadow, but none appears.
Any tips?