Why does this animation only work once? - ios

I have a UIView containing a UITapGestureRecognizer that triggers a method called handleLeftTap.
-(void)handleLeftTap {
self.player.direction = LEFT;
if(!self.isAnimating && self.toTile.isWalkable) {
for(UIView *currentView in self.subviews) {
CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
[moveAnimation setDuration:MOVE_ANIMATION_DURATION];
[moveAnimation setDelegate:self];
[moveAnimation setRemovedOnCompletion:NO];
[moveAnimation setFillMode:kCAFillModeForwards];
moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(currentView.center.x+TILE_WIDTH, currentView.center.y)];
[currentView.layer addAnimation:moveAnimation forKey:nil];
}
NSLog(#"animated\n");
}
}
When I tap on the screen for the first time, this animation works perfectly; every subview moves to the right by TILE_WIDTH pixels. However, if I tap the screen again, the animation doesn't work at all; no view moves. I stepped through this code with breakpoints and verified that this animation is being added to the layers. It's just that the animation isn't being applied or something like that. Is there a way to fix this?

Not quite sure, but are you sure that the second animation is a different location than the location after the first animation?

Related

Cannot see a CABasicAnimation when called from within a for loop

I have an animation which randomly moves a tile on a grid which works fine. However when I call in a for loop you dont see any of the animation. How do i get this to work?
for (i=0; i<10 ; i++){
// Call animation function
sleep(.5);
}
Animation looks like
CABasicAnimation *move;
[move setFromValue:[NSValue valueWithCGPoint:[button.layer position]]];
CGPoint toLoc = [button.layer position];
// modify toLoc by +/-70px in x/y direction
[move setToValue:[NSValue valueWithCGPoint:toLoc]];
[move setDuration:0.5];
[button.layer setPosition:toLoc];
I appreciate any advice/comments : )
When you start an animation, you need to let control return back to the system in order to see it perform the animation. Inserting a sleep is almost never the right thing to do in today's world of multi-threaded systems. It is certainly the reason that you are experiencing this issue.
Instead, you might consider doing something like this:
for (i=0; i<10 ; i++){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i * 0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Call animation function
});
}
Given that your sleep time is the same as your intended animation duration, it looks like you're just trying to repeat your animation, though. Instead, try just adding this line and removing the loop:
[move setRepeatCount:10];
You'll also need to instantiate the object and actually add the animation to the layer.
CABasicAnimation *move = [[CABasicAnimation alloc] init];
[move setFromValue:[NSValue valueWithCGPoint:[button.layer position]]];
CGPoint toLoc = [button.layer position];
// modify toLoc by +/-70px in x/y direction
[move setToValue:[NSValue valueWithCGPoint:toLoc]];
[move setDuration:0.5];
[move setRepeatCount:10];
[button.layer addAnimation:move forKey:#"position"];
I think the repetition logic should be handled by the animation function you are using.
If you are using CAAnimation you can use it's callback
"- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag"
to check if you should and then start a new animation or not from there.

Animating markers annotation on MapBox (iOS)

I'm trying to animate some markers initialised with a picture as follows RMMarker *marker = [[RMMarker alloc] initWithUIImage:lImage anchorPoint:lPoint];
I get a static image on my map with no problem.
Now, I need it to be more visible than other annotations, so I want to make it blink.
Here's what I already tried:
Creating scale animation on annotation layer
[CATransaction begin];
[CATransaction setAnimationDuration:0.70];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation *bounceAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
bounceAnimation.repeatCount = MAXFLOAT;
bounceAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1.0)];
bounceAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)];
bounceAnimation.removedOnCompletion = NO;
bounceAnimation.autoreverses = YES;
[layer addAnimation:bounceAnimation forKey:#"animateScale"];
[CATransaction commit];
This is working well, except that my callout view is also blinking as it shares the same layer (and Mapbox messes with the animation during the tracking mode, resulting in an incorrect orientation as it is reseted with every move of the user anyway).
Creating blink animation on annotation layer
[CATransaction begin];
[CATransaction setAnimationDuration:0.60];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.repeatCount = MAXFLOAT;
opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0];
opacityAnimation.toValue = [NSNumber numberWithFloat:0.3];
opacityAnimation.removedOnCompletion = NO;
opacityAnimation.autoreverses = YES;
[layer addAnimation:opacityAnimation forKey:#"animateOpacity"];
[CATransaction commit];
This is less noticeable, but the annotation orientation is correct this time. I still have my callout view blinking on the layer though.
What I want to do
I would need a way to animate the marker image without affecting the layer.
The best animation I'd use would be the first one I tried, without the rotation problem.
So far I couldn't animate the UIImage as it doesn't have the addAnimation: forKey: call.
I've never played much with animations, so any help or guidance would be appreciated.
You're on the right track with CABasicAnimation, as this is what is used for the animated user location annotations in the SDK. You can see this around here:
https://github.com/mapbox/mapbox-ios-sdk/blob/509fa7df46ebd654d130ab2f530a8e380bf2bd59/MapView/Map/RMMapView.m#L3593
Could you elaborate on this?
This is working well, except that my callout view is also blinking as it shares the same layer (and Mapbox messes with the animation during the tracking mode, resulting in an incorrect orientation as it is reseted with every move of the user anyway).
You do bring up a good point that the callout is a sublayer, so it would also blink. This use case hasn't been considered — is it possible for you to deselect your annotation and thus hide the callout?
But what is meant by the latter part?

How to trigger a CALayer's animation group on a trigger?

I have an animation on a CALayer which repeats indefinitely however I want to change it such that the animation is triggered by a certain timing event.
I can get it to work by removing and re-adding the animation event when the trigger occurs but this seems a bit kludgy, is there an alternative way without having to constantly remove and add the animation all the time?
Here's the code in sketch form:
- (void) init
{
….
CALayer *sublayer = …
[self.layer addSublayer:sublayer];
[self createAnimationGroup];
[sublayer addAnimation: self.animationGroup forKey:#"MyKey"];
}
- (void) createAnimationGroup
{
CAMediaTimingFunction *defaultCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
self.animationGroup = [CAAnimationGroup animation];
self.animationGroup.repeatCount = 0.0f; // was previously INFINITY;
<snip>
NSArray *animations = #[scaleAnimation, opacityAnimation];
self.animationGroup.animations = animations;
}
- (void) onTrigger
{
[self.layer.sublayers[0] removeAnimationForKey:#"MyKey"];
[self.layer.sublayers[0] addAnimation:self.animationGroup forKey:#"MyKey"];
}
My question is is the way I have implemented onTrigger ok, it works, but is there a way of triggering the animation directly rather than indirectly via removing and adding it?
Take a look at this suggestion from Apple. Specifically pausing and resuming an animation.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html#//apple_ref/doc/uid/TP40004514-CH8-SW15

Using Slide In & Slide Out Animations for Subview

I will start off by explaining that I have seen many questions and answers regarding this type of feature, but I am still having problems implementing it myself. I am using ARC, and am not using auto-layout or storyboard. I define my layouts with constraints in code, so the way I have been trying to implement my animation is a little different. Lastly, this is an iPad application.
To the specific problem at hand, I have a subview that starts off hidden but appears when an action takes place. I would like this subview to use the hidden feature, but slide in and out after it appears and before it is hidden. So far, I have gotten halfway there and am able to get the view to slide in without issue. Below is the code that accomplishes this.
detailView.hidden = NO;
// Perform Animation - Slide In
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.duration = kAnimationTimeout;
animation.removedOnCompletion = NO;
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(800.0, 0.0, 1.0)];
[detailView.layer addAnimation:animation forKey:nil];
However, I have been unsuccessful with trying getting the view to slide out before it is hidden. Below is the code that I added to attempt at completing this feature.
// Perform Animation - Slide Out
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.duration = kAnimationTimeout;
animation.removedOnCompletion = NO;
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-800.0, 0.0, 1.0)];
[detailView.layer addAnimation:animation forKey:nil];
detailView.hidden = YES;
The result I get is that the view simply disappears like it was hidden, which it always did. Do I need to remove one animation that is added to a view before I add a different animation? Or is my CATransform3DMakeTranslation incorrectly defined?
Turns out detailView.hidden was being called before the animation started. I resolved this by adding a selector with a delay that contained a method to hide my view.
[self performSelector:#selector(hideDetailView) withObject:nil afterDelay:.40];

UIGestureRecognizer on transformed layer

I'm working on and iPhone app, and have an issue with a gesture recognizer.
I've added an UITapGestureRecognizer on a view, then I transform the layer related to this view with CABasicAnimation. After this transformation, the gesture recognizer only works in the area occupied by the view before the transformation.
Hope this little description of my problem is understandable..
Here is some code :
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myViewTapped:)];
[self.myView addGestureRecognizer:tapGestureRecognizer];
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:#"position.y"];
[animation setFromValue:[NSNumber numberWithFloat:0]];
[animation setToValue:[NSNumber numberWithFloat: - 100]];
[animation setDuration:.3];
[animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.55 :-0.25 :.30 :1.4]];
animation.additive = YES;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.myView.layer addAnimation:animation forKey:nil];
How can I handle this issue ?
Thanks !
You are animating only the graphical part of the view (the CALayer), not the part responsable for user interaction (the UIView itself).
Your code move the layer and made it been drown elsewhere, but don't change the frame (or the bound+center).
You have 3 option (well maybe more, just I can think of these 3):
1) use the UIView-based animations [UIView animation...]
2) struct with you code but also relocare the view after the animation take place (but this may rise issues cause your layer will be moved also).
3) use your animation but put the gesture recognizer on a parent (bigger) view and then check the events there...

Resources