When I launch my application and go to flipSideViewController from mainViewController the image view in flipSideViewController will animate. I want to animate it every time the user
transitions from mainViewController to flipSideViewController.
Here's some code:
[self animate];
//Not sure where to put this instance method. Would I put this in a certain method? I have no idea what I would write for it!
- (void)animate
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.toValue = [NSNumber numberWithFloat:((-10*M_2_PI)/180)];
animation.duration = .3;
animation.autoreverses = YES;
animation.repeatCount = 4;
animation.speed = 9;
pwAnimation.removedOnCompletion = YES;
[_ImageView.layer addAnimation:pwAnimation forKey:#"rotation"];
}
Use ViewDidAppear delegate method of flipSideViewController to invoke your -animate method
Related
I have an insert animation for UICollectionCell (like slide-in), I called the insert animation in this method -collectionView:willDisplayCell:forItemAtIndexPath:.
Everything just works fine when I called this [self.collectionView reloadData]; to reload data. In this way, the animation will be shown currently.
------------
A
------------
<= inserted C (slide-in Animation)
------------
B
------------
But when I tried to add another animation to this inserted cell (like slide-down), things went wrong.
Instead of using -reloadData, I used [self.collectionView performBatchUpdates:completion:] and a custom UICollectionViewFlowLayout with initialLayoutAttributesForAppearingItemAtIndexPath method to implement the slide down animation.
------------
A
------------
------------ <= inserted C (Second, slide-in animation)
B (First, slide-down animation)
------------
In the cell, the log of animation's delegate method animationDidStop:finished: shows that the finished = NO
UICollectionCell:
- (void)setAnimationInLayer:(CALayer *)layer
{
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.translation.x"];
anim.fromValue = [NSNumber numberWithFloat:(layer.bounds.size.width * 0.33f)];
anim.toValue = [NSNumber numberWithFloat:0.0];
CABasicAnimation *opacityAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnim.fromValue = [NSNumber numberWithFloat:0];
opacityAnim.toValue = [NSNumber numberWithFloat:1];
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.animations = [NSArray arrayWithObjects:anim,opacityAnim, nil];
animGroup.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
animGroup.duration = 5.5f;
animGroup.delegate = self;
[layer addAnimation:animGroup forKey:nil];
}
- (void)animationDidStart:(CAAnimation *)animation
{
NSLog(#"-------> %s",__func__);
NSLog(#"%f",[self.articlesCollection.layer presentationLayer].frame.origin.x);
}
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
NSLog(#"-------> %s finished: %d",__func__,finished);
NSLog(#"%f",[self.articlesCollection.layer presentationLayer].frame.origin.x);
}
To sum up, when I use -performBatchUpdates:completion instead of -reloadData, every animation of UICollectionView disappeared.
So the question is what situation may cause an animation disappear?
I mean looks like it has been removed from the layer it is attached to. But I didn't do that. Any idea?
How to identify CAAnimation after completion? I tried KVO by setting a string value as animationID and checking it in animationDidStop method. But KVO returns an integer sometimes instead of a string animationID and the app is crashing.
Here is my code:
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point duration:(NSTimeInterval)duration animationID:(NSString*)animationid
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
[animation setDuration:duration];
animation.toValue = [NSValue valueWithCGPoint:point];
animation.delegate=self;
[animation setValue:animationid forKey:#"animationID"];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
// Update the layer's position so that the layer doesn't snap back when the animation completes.
layer.position = point;
// Add the animation, overriding the implicit animation.
[layer addAnimation:animation forKey:#"position"];
}
and callback is:
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
if (flag) {
id animationIDString=[animation valueForKey:#"animationID"];
// this is coming as an int value sometimes and the code below this fails ..
if([animationIDString isEqual:#"animation1"]) {
//animation is animation1
_movieBalloonImageView.hidden=NO;
_storiesBalloonImageView.hidden=NO;
_rhymesBalloonImageView.hidden=NO;
[self.navigationController popViewControllerAnimated:NO];
}
}
}
What can be the possible reason? Is it because the animation gets destroyed before this delegate gets called?
I am setting removedOnCompletion to NO and fillmode to kCAFillModeForwards.But the code is not working as expected.
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
Or is there any alternative method to do this? I am calling the moveLayer method in several places in the viewController.
I am having an issue with iOS CABasicAnimation. No matter what I do, I cannot get the methods animationDidStart: and animationDidStop:finished: to fire. My class is subclassing CAShapeLayer and is performing the animations inside of it:
- (void)start{
[self removeAllAnimations];
CABasicAnimation *pathAnimation = [self makeAnimationForKey:#"strokeEnd"];
[self addAnimation:pathAnimation forKey:#"strokeEnd"];
}
- (CABasicAnimation *)makeAnimationForKey:(NSString *)key {
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:key];
anim.fromValue = [NSNumber numberWithFloat:0.f];
anim.toValue = [NSNumber numberWithFloat:1.f];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim.duration = self.duration;
anim.delegate = self;
return anim;
}
- (void)animationDidStart:(CAAnimation *)anim{
NSLog(#"HERE START");
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(#"HERE STOP");
}
Any tips or help would be appreciated, thanks in advance!
you must set delegate before assigning animation to layer:
popIn.delegate = self;
[annotation.layer addAnimation:popIn forKey:#"popIn"];
Swift 5.4.2
popIn.delegate = self
annotation.layer.add(transition, forKey:"popIn")
Ok so it turns in my subclass I had a property called duration. Even though it is not documented as being apart of CALayer, duration is a part of one of it's protocols called CAMediaTiming. The methods were never fired because the property was being overwritten via my subclass.
I'm trying to create a custom loading animation. The animation consists of two parts. First, 5 lines are drawn (animated) outwards. When the lines are finished drawing, they are faded away and the cycle is started over. In order to get this sequential behavior with two animations, I am using two CABasicAnimations, both of which have the same delegate. In the delegate method animationDidStop:, I dispatch to the correct animation (animation 2 if animation 1 just finished, and vise versa). Here is the relevant code:
-(void)startAnimating{//this method launches the line growing animation
self.hidden = NO;
BOOL delegateSet = NO;
animationPhase = 1;
for(CAShapeLayer * pathLayer in _lines){
[pathLayer removeAllAnimations];
pathLayer.strokeColor = _strokeColor.CGColor;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = .95;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
if(!delegateSet){//just set one delegate
pathAnimation.delegate = self;
delegateSet = YES;
}
[pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
}
}
//this delegate method dispatches the next animation
- (void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
if(animationPhase == 1)[self fadeLines];
else if(animationPhase == 2)[self startAnimating];
}
-(void)fadeLines{//this method launches the fading animation
self.hidden = NO;
BOOL delegateSet = NO;
animationPhase = 2;
for(CAShapeLayer * pathLayer in _lines){
[pathLayer removeAllAnimations];
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) pathLayer.strokeColor;
strokeAnim.toValue = (id) [UIColor clearColor].CGColor;
strokeAnim.duration = .2;
if(!delegateSet){//just set one delegate
strokeAnim.delegate = self;
delegateSet = YES;
}
[pathLayer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
pathLayer.strokeColor = [UIColor clearColor].CGColor;
}
}
So this all works great when it's run on it's own (not while other things are being done in the background). But when I use it as a loading animation (while other things are being done in the background), the first initial grow animation is performed, but animationDidStop: is never called.
I've tried using transactions with completion blocks instead of using the delegate pattern, but I get the same issue. I can't use CAAnimationGroup with appropriate beginTimes because I need the animations to loop.
Another thing to note is if I just perform the growing animation with a high repeat count, everything works fine. The only issue is the sequential looping of the two animations. If anyone knows how I can fix this, or knows a different way to sequentially loop two CAAnimations, I will be very grateful.
I have a custom UIView subclass (ValveStatusView). It is a subview placed in a custom 'UITableViewCellsubclass (ValveCell) . Upon receivingawakeFromNibinValveStatusView`, I set up a number of sublayers, one of which is animated. The animation setup code looks like this:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(32, 64 + 6)];
animation.duration = 0.75;
animation.repeatCount = HUGE_VALF;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
[drop addAnimation:animation forKey:#"position"];
It makes a "drop" image repeatedly fall from a faucet, forever. These cells are embedded in a UITableView and then in a tabbed view. When I navigate to the other tab, and then back, my animation isn't running anymore. I presume when the view stack containing my view's is no longer mapped to the window, it shuts down the animation. Is that a correct assumption? Assuming it is, what's the idiomatic way to get it running again when the view is mapped again?
I looked through the UIView methods, but nothing jumped out as the obvious "hook" to get it running again. I admit to skimming through them, not exhaustively reading it all.
You should have mentioned in your question that the view containing the animated layer is a subclass of UITableViewCell. :) Because, that is actually the cause of your animation being stopped. UITableViewCell removes the animations from its sublayers to avoid unnecessary computations when a cell is removed from tableview for later reuse. To solve your problem you can override the prepareForReuse method of your cell and restart your animation there. So you should do something like this:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
// some initial setup here...
[self addAnimation];
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
[self addAnimation];
}
- (void)addAnimation {
// remove previous animations
[drop removeAllAnimations];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(32, 64 + 6)];
animation.duration = 0.75;
animation.repeatCount = HUGE_VALF;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
[drop addAnimation:animation forKey:#"position"];
}
Hope this helps.
Try putting your code in - (void) viewWillAppear: method instead of awakeFromNib, this way it will be triggered whenever the view is shown.
EDIT:
OK, I see now, you can do that by using NSNotificationCenter:
In the awakeFromNib of your customCell add an observer:
- (void) awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(startAnimation:)
name:#"addAnimation"
object:nil];
}
in the startAnimation: method you will put of course your animation code.
And in the viewWillAppear of your UITableViewController you post the notification to restart the animations.
[[NSNotificationCenter defaultCenter]
postNotificationName:#"addAnimation"
object:self];
Somewhere in the CustomCell you must remove the observer, or the cell will never be unloaded.
I worded the original question poorly apparently. I've tried with edits to clarify the situation. Regardless of UITableViewCell containment or not, my desire was to find a solution that was indifferent to that. Encapsulation and all that.
The following code, added to ValveStatusView (my subclass of UIView).
- (void) didMoveToWindow {
if (self.window != nil) {
[self startDropAnimation];
}
}
does the trick. startDropAnimation is inspired by the refactoring suggested by #Davlat above:
- (void)startDropAnimation {
CALayer* drop = self.activeFaucet.sublayers.firstObject;
[drop removeAllAnimations];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(32, 64 + 6)];
animation.duration = 0.75;
animation.repeatCount = HUGE_VALF;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
[drop addAnimation:animation forKey:#"position"];
}
didMoveToWindow was the hook I was looking for (I think).