CALayer mask Layer animating - uikit

I want to mask CALayer with another layer whose frame I want to keep changing in every draw call thereby animating. Is it possible to do change the frame of mask layer in every draw call rather than making a new mask layer?

Try the following code. This code will run on Cocoa App for macOS. Kindly change the classes accordingly if you are going for Cocoa Touch for iOS. I hope this works for you.
// ViewController.h
import
#interface ViewController : NSViewController{
CALayer * maskLayer;
CALayer * Layer;
}
// ViewController.m
#implementation ViewController
-(void)viewDidLoad{
[super viewDidLoad];
maskLayer = [CALayer layer];
Layer = [CALayer layer];
Layer.backgroundColor = [[NSColor blueColor] CGColor];
Layer.frame = CGRectMake(20, 20, 720, 900);
Layer.contents = [NSImage imageNamed:#"bg1"];
maskLayer.frame = CGRectMake(20, 20, 720, 900);
maskLayer.opacity = 0.5;
[maskLayer mask];
maskLayer.contents = [NSImage imageNamed:#"bg"];
[self.view.layer addSublayer:Layer];
[self.view.layer addSublayer:maskLayer];
[self maskLayerAnimation];
}
-(void)maskLayerAnimation{
[CATransaction begin];
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 5.0;
[CATransaction setCompletionBlock:^{
NSLog(#"animation completed");
maskLayer.frame = CGRectMake(20, 20, 720, 700);
maskLayer.contents = [NSImage imageNamed:#"bg2"];
}];;
[maskLayer addAnimation:fadeAnim forKey:#"opacity"];
[CATransaction commit];
}

Related

Change speed of "marching ants" effect after every 10 seconds

I'd like to change the Speed of my "Marching ants" effect after it has looped every 10 times. So it's looping the animation with a duration of 1, after the 10 loops it should change the duration to 0.95, after 10 more seconds to 0.9 and so on....
Do you have an idea how I could do this?
Here's the code for my current animation:
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.bounds = CGRectMake(0, 0, self.view.bounds.size.width, 20);
lineLayer.position = self.view.center;
lineLayer.strokeColor = [UIColor whiteColor].CGColor;
lineLayer.lineDashPattern = #[#100];
lineLayer.lineWidth = CGRectGetHeight(lineLayer.bounds);
UIBezierPath *linePath = [UIBezierPath bezierPath];
[linePath moveToPoint:CGPointMake(0, CGRectGetMidY(lineLayer.bounds))];
[linePath addLineToPoint:CGPointMake(CGRectGetMaxX(lineLayer.bounds), CGRectGetMidY(lineLayer.bounds))];
lineLayer.path = linePath.CGPath;
// [self.view.layer addSublayer:lineLayer];
NSNumber *totalDashLenght = [lineLayer.lineDashPattern valueForKeyPath:#"#sum.self"]; // KVC is awesome :)
CABasicAnimation *animatePhase = [CABasicAnimation animationWithKeyPath:#"lineDashPhase"];
animatePhase.byValue = totalDashLenght; // using byValue means that even if the layer has a shifted phase, it will shift on top of that.
animatePhase.duration = 0.5;
animatePhase.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animatePhase.repeatCount = 10;
// [lineLayer addAnimation:animatePhase forKey:#"marching ants"];
// Create the shape layer
shapeLayer = [CAShapeLayer layer];
CGRect shapeRect = CGRectMake(0.0f, 0.0f, 2200.0f, 2000.0f);
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake(160.0f, 1350.0f)];
[shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
[shapeLayer setStrokeColor:[[UIColor whiteColor] CGColor]];
[shapeLayer setLineWidth:20.0f];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setLineDashPattern:
[NSArray arrayWithObjects:[NSNumber numberWithInt:200],
[NSNumber numberWithInt:100],
nil]];
// Setup the path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, shapeRect);
[shapeLayer setPath:path];
CGPathRelease(path);
// Set the layer's contents
//[shapeLayer setContents:(id)[[UIImage imageNamed:#"GEIST.jpg"] CGImage]];
[[[self view] layer] addSublayer:shapeLayer];
if ([shapeLayer animationForKey:#"linePhase"])
[shapeLayer removeAnimationForKey:#"linePhase"];
else {
CABasicAnimation *dashAnimation;
dashAnimation = [CABasicAnimation
animationWithKeyPath:#"lineDashPhase"];
[dashAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];
[dashAnimation setToValue:[NSNumber numberWithFloat:300.0f]];
[dashAnimation setDuration:1];
[dashAnimation setRepeatCount:10];
[shapeLayer addAnimation:dashAnimation forKey:#"linePhase"];
Just set yourself up to detect the end of your animation, either with a delegate method or with a completion block on the transaction. When your animation completes its ten repetitions, you can schedule a new animation with a shorter duration.
(And remember being small, playing under the table and dreaming...)

How do I add an AVPlayer to a QWidget?

On Mac, I want to use AVPlayer to play video in my Qt 4.8 application since it has features that Phonon::VideoPlayer doesn't have.
How do I add the AVPlayerLayer to a QWidget so it becomes visible? I'm doing the following:
pimpl->player = [[AVPlayer alloc] initWithURL:[NSURL fileURLWithPath:videoFile]];
pimpl->player.actionAtItemEnd = AVPlayerActionAtItemEndPause;
pimpl->player.rate = 0;
pimpl->playerLayer = [[AVPlayerLayer playerLayerWithPlayer:pimpl->player] retain];
[pimpl->playerLayer setFrame: CGRectMake(0, 0, rect().width(), rect().height())];
NSView* view = (NSView*)winId();
[[view layer] addSublayer:pimpl->playerLayer];
When I play my video, I hear it, but I don't see it. Any idea what I'm doing wrong?
Rather than using winId(), use QMacCocoaViewContainer, but QMacCocoaViewContainer has a bug which will cause the NSView to not be displayed in Qt 4.8 unless setAttribute(Qt::WA_NativeWindow); is set.
As well, the NSView may not have a layer, so a layer need to be created.
[pimpl->view setWantsLayer:YES];
[pimpl->view makeBackingLayer];
So in total it looks like:
pimpl->view = [[NSView alloc] initWithFrame: CGRectMake(0, 0, w, h)];
[pimpl->view setHidden:NO];
[pimpl->view setNeedsDisplay:YES];
[pimpl->view setWantsLayer:YES];
[pimpl->view makeBackingLayer];
viewFrame = new QMacCocoaViewContainer(NULL, this);
viewFrame->setGeometry(rect());
viewFrame->setVisible(true);
viewFrame->setCocoaView(pimpl->view);
NSString* videoFile = [NSString stringWithCString:filename.toUtf8().data() encoding:NSUTF8StringEncoding];
pimpl->player = [[AVPlayer alloc] initWithURL:[NSURL fileURLWithPath:videoFile]];
pimpl->player.actionAtItemEnd = AVPlayerActionAtItemEndPause;
pimpl->player.rate = 0;
pimpl->playerLayer = [[AVPlayerLayer playerLayerWithPlayer:pimpl->player] retain];
[pimpl->playerLayer setFrame: CGRectMake(0, 0, w, h)];
[pimpl->playerLayer setHidden: NO];
CALayer* layer = [pimpl->view layer];
[layer addSublayer:pimpl->playerLayer];

Fade bottom of UIScrollView to transparent

I have a UIScrollView, and I need the bottom to fade to transparent, so that it does not abruptly cut off the content. The background of the UIScrollView is a custom color. This is what I have so far, but the layer is showing up white, instead of the custom color. This is what I am going for, but only on the bottom.
:
Here is what I have so far:
maskLayer = [CAGradientLayer layer];
CGColorRef firstColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:1.0].CGColor;
CGColorRef secondColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.8].CGColor;
CGColorRef thirdColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.2].CGColor;
CGColorRef fourthColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.0].CGColor;
maskLayer.colors = [NSArray arrayWithObjects:(__bridge id)firstColor, (__bridge id)secondColor, (__bridge id)thirdColor, (__bridge id)fourthColor, nil];
maskLayer.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.2],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0], nil];
maskLayer.frame = CGRectMake(0, 285, self.loginScrollView.frame.size.width, 40);
maskLayer.anchorPoint = CGPointZero;
[self.loginScrollView.layer addSublayer:maskLayer];
For some reason, the layer is showing up as white, and only half the width of the scrollView.
The approach I used was to subclass UIScrollView, and create the mask layer in the layoutSubviews method.
Here's my code, which fades the top and bottom of the UIScrollView from the background colour to transparent:
#import "FadingScrollView.h"
#import <QuartzCore/QuartzCore.h>
static float const fadePercentage = 0.2;
#implementation FadingScrollView
// ...
- (void)layoutSubviews
{
[super layoutSubviews];
NSObject * transparent = (NSObject *) [[UIColor colorWithWhite:0 alpha:0] CGColor];
NSObject * opaque = (NSObject *) [[UIColor colorWithWhite:0 alpha:1] CGColor];
CALayer * maskLayer = [CALayer layer];
maskLayer.frame = self.bounds;
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0,
self.bounds.size.width, self.bounds.size.height);
gradientLayer.colors = [NSArray arrayWithObjects: transparent, opaque,
opaque, transparent, nil];
// Set percentage of scrollview that fades at top & bottom
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:fadePercentage],
[NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
[maskLayer addSublayer:gradientLayer];
self.layer.mask = maskLayer;
}
#end
If you just want to fade the bottom, change this line:
// Fade bottom of scrollview only
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
When I was implementing this myself, I found this SO question helpful, and this gist on github.
EDIT: I've put this code up on github, see here.
Really like Steph Sharp's implementation, I converted it to swift and thought I'd share in case anyone else needs it.
let fadePercentage = CGFloat(0.2)
override func layoutSubviews() {
super.layoutSubviews()
var transparent = UIColor.clearColor().CGColor
var opaque = UIColor.blackColor().CGColor
var maskLayer = CALayer()
maskLayer.frame = self.bounds
var gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0, self.bounds.size.width, self.bounds.size.height)
gradientLayer.colors = [transparent, opaque, opaque, transparent]
gradientLayer.locations = [0, fadePercentage, 1 - fadePercentage, 1]
maskLayer.addSublayer(gradientLayer)
self.layer.mask = maskLayer
}
Here goes the swift 3 version of Steph's answer:
let fadePercentage: Double = 0.2
override func layoutSubviews() {
super.layoutSubviews()
let transparent = UIColor.clear.cgColor
let opaque = UIColor.black.cgColor
let maskLayer = CALayer()
maskLayer.frame = self.bounds
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: self.bounds.origin.x, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
gradientLayer.colors = [transparent, opaque, opaque, transparent]
gradientLayer.locations = [0, NSNumber(floatLiteral: fadePercentage), NSNumber(floatLiteral: 1 - fadePercentage), 1]
maskLayer.addSublayer(gradientLayer)
self.layer.mask = maskLayer
}
Theres also this answer which does not include any subclassing.

NSView with gradient background AND shadow

I want my NSView to have both gradient background and shadow, so I subclassed NSView and overrode the - (void)awakeFromNib method like this:
[self setWantsLayer:YES]; // view's backing store is using a Core Animation Layer
CAGradientLayer *backgroundGradient = [[CAGradientLayer alloc] init];
backgroundGradient.colors = #[
(__bridge id)CGColorCreateGenericGray(0.85, 1.0),
(__bridge id)CGColorCreateGenericGray(0.94, 1.0)
];
backgroundGradient.masksToBounds = NO;
CALayer *shadowLayer = [CALayer layer];
shadowLayer.shadowColor = CGColorCreateGenericGray(0.5, 1.0);
shadowLayer.shadowOffset = NSMakeSize(0, 0.0);
shadowLayer.shadowRadius = 10.0;
shadowLayer.shadowOpacity = 1.0;
shadowLayer.masksToBounds = NO;
[backgroundGradient addSublayer:shadowLayer];
[self setLayer:backgroundGradient];
However, only the gradient was shown. In addition, if I try [self setLayer:shadowLayer] then the shadow is shown (but of course, no gradient).
How to show both the gradient and the shadow? Thanks a lot!

drawLayer not called when subclassing CALayer

I have a simple CALayer subclass (BoxLayer) with this drawLayer method:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
NSLog(#"drawLayer");
NSGraphicsContext *nsGraphicsContext;
nsGraphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx
flipped:NO];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:nsGraphicsContext];
// ...Draw content using NS APIs...
NSPoint origin = { 21,21 };
NSRect rect;
rect.origin = origin;
rect.size.width = 128;
rect.size.height = 128;
NSBezierPath * path;
path = [NSBezierPath bezierPathWithRect:rect];
[path setLineWidth:4];
[[NSColor whiteColor] set];
[path fill];
[[NSColor grayColor] set];
[path stroke];
[NSGraphicsContext restoreGraphicsState];
}
I then have this awakeFromNib in my NSView subclass:
- (void)awakeFromNib {
CALayer* rootLayer = [CALayer layer];
[self setLayer:rootLayer];
[self setWantsLayer:YES];
box1 = [CALayer layer];
box1.bounds = CGRectMake(0, 0, 70, 30);
box1.position = CGPointMake(80, 80);
box1.cornerRadius = 10;
box1.borderColor = CGColorCreateGenericRGB(255, 0, 0, 1);
box1.borderWidth = 1.5;
[rootLayer addSublayer:box1];
box2 = [BoxLayer layer];
[box2 setDelegate:box2];
[box2 setNeedsDisplay];
[rootLayer addSublayer:box2];
}
My drawLayer is never called though, but why not?
Thank you
Possibly because the frame of your layer seems to be equal to CGRectZero, so the OS might think it's invisible so it doesn't need to draw it.
On a side note, why are going the complicated way of setting the layer's delegate to itself and implementing drawLayer:inContext: instead of using drawInContext: directly?

Resources