Animate CALayer hide - objective-c

I'm trying to hide a CALayer after a few microseconds and I'm using CABasicAnimation to animate the hide.
At the moment I'm trying to use
[aLayer setHidden:YES];
CABasicAnimation * hideAnimation = [CABasicAnimation animationWithKeyPath:#"hidden"];
[hideAnimation setDuration:aDuration];
[hideAnimation setFromValue:[NSNumber numberWithBool:NO]];
[hideAnimation setToValue:[NSNumber numberWithBool:YES]];
[hideAnimation setBeginTime:0.09];
[hideAnimation setRemovedOnCompletion:NO];
[hideAnimation setDelegate:self];
[alayer addAnimation:hideAnimation forKey:#"hide"];
But when I run this, the layer is hidden immediately, rather than waiting for the desired beginTime.
I'm uncertain about my keyPath as "hidden" but couldn't find any other option and the documentation does state that the hidden property of a CALayer is animatable.
What's the correct way to achieve what I'm looking for?

Try animating the opacity property instead. Go from 1.0 to 0.0 and you should get the effect you want.

From CAMediaTiming.h, it says about beginTime property:
The begin time of the object, in
relation to its parent object, if
applicable. Defaults to 0.
You should use CACurrentMediaTime() + desired time offset.
[hideAnimation setBeginTime:CACurrentMediaTime() + 0.09];

I'm sure this is too late to do the original poster any good, but it may help others. I've been trying to do something similar, except to make the animation implicit when the hidden property is changed. As Tom says, animating opacity doesn't work in that case, as the change to the layer's hidden property seems to take effect right away (even if I delay the animation with beginTime).
The standard implicit action uses a fade transition (CATransition, type = kCATransitionFade), but this operates on the whole layer and I want to perform another animation at the same time, which is not a compatible operation.
After much experimentation, I finally noticed #Kevin's comment above and --- hello! --- that actually works! So I just wanted to call it out so the solution is more visible to future searchers:
CAKeyframeAnimation* hiddenAnim = [CAKeyframeAnimation animationWithKeyPath:#"hidden"];
hiddenAnim.values = #[#(NO),#(YES)];
hiddenAnim.keyTimes = #[#0.0, #1.0];
hiddenAnim.calculationMode = kCAAnimationDiscrete;
hiddenAnim.duration = duration;
This delays the hiding until the end of the duration. Combine it with other property animations in a group to have their effects seen before the layer disappears. (You can combine this with an opacity animation to have the layer fade out, while performing another animation.)
Thank you, Kevin!

swift 4
let keyframeAnimation = CAKeyframeAnimation(keyPath: "hidden")
keyframeAnimation.calculationMode = kCAAnimationDiscrete
keyframeAnimation.repeatCount = 1.0
keyframeAnimation.values = [true, false,true,false,true]
keyframeAnimation.keyTimes = [0.0, 0.25,0.5,0.75, 1.0]
keyframeAnimation.duration = 30.0 //duration of the video in my case
keyframeAnimation.beginTime = 0.1
keyframeAnimation.isRemovedOnCompletion = false
keyframeAnimation.fillMode = kCAFillModeBoth
textLayer.add(keyframeAnimation, forKey: "hidden")

CABasicAnimation *endAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
endAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[endAnimation setFromValue:[NSNumber numberWithFloat:1]];
[endAnimation setToValue:[NSNumber numberWithFloat:0.0]];
[endAnimation setBeginTime:AVCoreAnimationBeginTimeAtZero];
endAnimation.duration = 5;
endAnimation.removedOnCompletion = NO;
[alayer addAnimation:endAnimation forKey:nil];

Related

execute animation when access to a property

this is a simple code form Brad Larson u-tunes course ;)
CABasicAnimation *move = [CABasicAnimation animationWithKeyPath:#"position"];
move.duration = 1.0f;
move.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
move.removedOnCompletion = NO;
move.fillMode = kCAFillModeForwards;
CGPoint currentPosition = l.position;
CGPoint newPosition = CGPointMake(currentPosition.x + 60.0f, currentPosition.y + 60.0f);
move.toValue = [NSValue valueWithCGPoint:newPosition];
[l addAnimation:move forKey:#"position"];
l.position = newPosition;
in the last row i change the position to reflect the final state of layer because animation does not.
But when i execute this code the animation isn't executed and layer move (in 1/4 of sec) to newposition.
someone can explain me how to animate layer's position correctly?
a second question...when i run this code...every subsequent access to property "position" will perform the same animation?
thanks.
Ditch the last line, the l.position = newPosition;. Your animation will already take care of that, and by using that property setter, you’re implicitly giving the layer Core Animation’s default .25-second action.
Also, no, subsequent changes in the position of your layer will not use your 1-second animation. The properties you’re using look pretty much identical to the default animation, though, aside from the duration; a quicker way to accomplish what you’re doing would be something like this.
CGPoint currentPosition = l.position;
CGPoint newPosition = CGPointMake(currentPosition.x + 60.0f, currentPosition.y + 60.0f);
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
l.position = newPosition;
[CATransaction commit];

Replicate radiating circles like user location on MKMapView

Is it possible to have the radiating circles like the user location annotation. So that custom annotations have radiating circles of other colors. If not is there a hack way to make it work?
Check this out. You may able to do what you need with that. With a combination of using Core Animation and subclassing MKCircleView or MKOverlayView.
http://yickhong-ios.blogspot.com/2012/04/animated-circle-on-mkmapview.html
It is possible to create a custom subclass of UIView that does this. A UIView with two sublayers, one for the center ball and one for the expanding rings. The ring layer and the ball layer can be created by subclassing CALayer and overriding drawInContext: so you can get any colors you want. Code to animate the rings so that they expand and fade out at the same time could use a CAAnimationGroup like this:
// expand the ring from the ball size to the ring's max size
CABasicAnimation *sizeAnim = [CABasicAnimation animationWithKeyPath:#"bounds"];
sizeAnim.fromValue = [NSValue valueWithCGRect:ballBounds];
sizeAnim.toValue = [NSValue valueWithCGRect:ringBoundsMax];
sizeAnim.duration = kRingExpansionTime;
// fade out the ring part way thru the animation
CABasicAnimation* alphaAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
alphaAnim.fromValue = [NSNumber numberWithFloat:1];
alphaAnim.toValue = [NSNumber numberWithFloat:0];
alphaAnim.beginTime = kRingExpansionTime * 0.7f; // start part way thru
alphaAnim.duration = kRingExpansionTime - alphaAnim.beginTime;
CAAnimationGroup* group = [CAAnimationGroup animation];
group.duration = kRingExpansionTime;
group.repeatCount = HUGE_VALF; // repeat forever
group.animations = [NSArray arrayWithObjects:sizeAnim, alphaAnim, nil];
[ringLayer addAnimation:group forKey:nil];

Animating a gaussian blur using core animation?

I'm trying to animate something where it's initially blurry then it comes into focus. I guess it works OK, but when the animation is done it's still a little blurry. Am I doing this wrong?
CABasicAnimation* blurAnimation = [CABasicAnimation animation];
CIFilter *blurFilter = [CIFilter filterWithName:#"CIGaussianBlur"];
[blurFilter setDefaults];
[blurFilter setValue:[NSNumber numberWithFloat:0.0] forKey:#"inputRadius"];
[blurFilter setName:#"blur"];
[[self layer] setFilters:[NSArray arrayWithObject:blurFilter]];
blurAnimation.keyPath = #"filters.blur.inputRadius";
blurAnimation.fromValue = [NSNumber numberWithFloat:10.0f];
blurAnimation.toValue = [NSNumber numberWithFloat:1.0];
blurAnimation.duration = 1.2;
[self.layer addAnimation:blurAnimation forKey:#"blurAnimation"];
Your problem is that the animation stops and is automatically removed, but the filter lingers with the tiniest of blur applied.
What you want to do is to remove the blur filter when the animation completes. You need to add a delegate to the CABasicAnimation instance and implement the -[id<CAAnimationDelegate> animationDidStop:finished:] method.
If you let self be the delegate in this case it should be fairly simple, add this line before adding the animation to your layer:
blurAnimation.delegate = self;
And the callback is equally simple:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[[self layer] setFilters:nil];
}
If you're looking for an optimized way to animate a blur then I recommend creating a single blurred image of your view and then fading the blurred image from alpha 0 to 1 over the top of your original view. Seems nice and fast in tests.

NSView CALayer Opacity Madness

It seems as though the layer's properties of my NSView are sometimes not editable/wrong. In the code below, the animation works perfectly, and all appears normal. The output from the NSlogs are always :
anim over opacity = 1.00000
first opacity = 0.50000
current opacity = 0.00000
updated opacity = 0.00000
The first two logs look right, so even at animation did stop, the layer seems to operate normally. However, some time later, when I check the opacity it magically turned to 0. Further wrong, when I set the layer's opacity to 1, and check it immediately after, it still is 0. How is that possible?
I goofed around with setneedsdisplay in the layer and setneedsdisplay:YES in nsview and that
didn't help. Any suggestions are greatly appreciated.
- (void) someSetupAnimationMethod {
aLayer = [CALayer layer];
[theView setWantsLayer:YES];
[theView setLayer:aLayer];
[aLayer setOpacity:0.0];
CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacity.byValue = [NSNumber numberWithFloat:1.0];
opacity.duration = 0.3;
opacity.delegate = self;
opacity.fillMode = kCAFillModeForwards;
opacity.removedOnCompletion = NO;
[opacity setValue:#"opacity done" forKey:#"animation"];
[aLayer addAnimation:opacity forKey:nil];
}
- (void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if([[anim valueForKey:#"animation"] isEqualToString:#"opacity done"]) {
NSLog(#"anim over opacity = %f", aLayer.opacity);
aLayer.opacity = 0.5;
[aLyaer removeAllAnimations];
NSLog(#"first opacity = %f", aLayer.opacity);
}
}
- (void) someLaterMethod {
NSLog(#"current opacity = %f", aLayer.opacity);
aLayer.opacity = 1.0;
NSLog(#"updated opacity = %f", aLayer.opacity);
}
You're breaking a fundamental CALayer/NSView rule by creating a layer-backed view and then trying to manipulate the layer directly.
aLayer = [CALayer layer];
[theView setWantsLayer:YES];
[theView setLayer:aLayer];
When you tell the view to use a layer before calling setLayer:, the view becomes "layer-backed" -- it is simply using the layer as a buffer and all drawing that you want to do should be done through the usual drawRect: and related methods. You are not allowed to touch the layer directly. If you change the order of those calls:
[theView setLayer:aLayer];
[theView setWantsLayer:YES];
You now have a "layer-hosting" view, which means that the view just provides a space on the screen for the layer to be drawn into. In this case, you do your drawing directly into the layer. Since the contents of the layer become the contents of the view, you cannot add subviews or use the view's drawing mechanisms.
This is mentioned in the -[NSView setWantsLayer:] documentation; I also found an old cocoa-dev thread that explains it pretty well.
I'm not certain that this will fix your problem, but as far as I can see you're doing everything else correctly, such as setting the fill mode and updating the value after the animation finishes.

What's the easiest way to animate a line?

I am creating an app that involves animating lines within a workspace over time. My current approach for this is to use code like this in drawRect:
CGContextSetStrokeColor(context, black);
CGContextBeginPath(context);
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, finalPoint.x, finalPoint.y);
CGContextStrokePath(context);
...and then just setting a timer to run every 0.05 seconds to update finalPoint and call setNeedsDisplay.
I'm finding this approach (when there's 5ish lines moving at once) slows down the app terribly, and even with such a high refresh frequency, still appears jerky.
There must be some better way to perform this very simple line drawing in an animated line - i.e. saying that I want a line to start at x1, y1 and stretching to x2, y2 over a given length of time. What are my options for this? I need to make this perform faster and would love to get rid of this clunky timer.
Thanks!
Use CAShapeLayers, and a combination of CATransactions and CABasicAnimation.
You can add a given path to a shapeLayer and let it do the rendering.
A CAShapeLayer object has two properties called strokeStart and strokeEnd, which defines where along the path the end of the line should render. The defaults are 0.0 for strokeStart, and 1.0 for strokeEnd.
If you set up your path so that strokeEnd initially starts at 0.0, you will see no line.
You can then animate, from 0.0 to 1.0, the strokeEnd property and you will see the line lengthen.
To change CAShapeLayer's implicit 0.25s default animation timing, you can add a function to the class, like so:
-(void)animateStrokeEnd:(CGFloat)_strokeEnd {
[CATransaction begin];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.duration = 2.0f; //or however long you want it to happen
animation.fromValue = [NSNumber numberWithFloat:self.strokeEnd]; // from the current strokeEnd value
animation.toValue = [NSNumber numberWithFloat:_strokeEnd]; //to the end of the path
[CATransaction setCompletionBlock:^{ self.strokeEnd = _strokeEnd }];
[self addAnimation:animation forKey:#"animateStrokeEnd"];
[CATransaction commit];
}
You can pass any value from 0.0f to 1.0f as the value of _strokeEnd.
The setCompletionBlock: ensures that the value you are passing is explicitly set after the animation completes.