I need to fade in my CALayers. Here's my code:
for(int i = 0; i < viewArray.count; i++)
{
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeIn.duration = 0.3;
fadeIn.beginTime = i;
fadeIn.fromValue = [NSNumber numberWithFloat:0.0];
fadeIn.toValue = [NSNumber numberWithFloat:0.8];
fadeIn.removedOnCompletion = NO;
fadeIn.delegate = self;
[((CALayer*)[viewArray objectAtIndex:i]) addAnimation:fadeIn forKey:nil];
}
However, only the first two objects properly fades, all other objects do not fade at all. I've noticed that when I put a breakpoint at animationDidStop, most of the animations that have not started already stopped (even the animations that are supposed to start 60 seconds in are stopped within the first couple of seconds). I'm not exactly sure what's going on. I can see it properly when I manually set each CALayer's opacity to 1 but not when animating.
You are starting all of the animations at the same time, because the call to addAnimation:forKey: does not synchronously wait until the animation is complete.
I have checked it and it seems you have to set the beginTime in a following way
fadeIn.beginTime = CACurrentMediaTime() + i;
Related
I programmed my own view containing an imageview which should be rotating. Here is my rotation animation:
- (void)startPropeller
{
//_movablePropeller = [[UIImageView alloc] initWithFrame:self.frame];
//_movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
//[self addSubview:self.movablePropeller];
self.hidden = NO;
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0.0f];
rotation.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
rotation.cumulative = true;
rotation.duration = 1.2f; // Speed
rotation.repeatCount = INFINITY; // Repeat forever. Can be a finite number.
[self.movablePropeller.layer removeAllAnimations];
[self.movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
}
And here is how I start it:
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:self.view.frame andStyle:LoadingPropellerStyleNoBackground];
self.loadingPropeller.center=self.view.center;
[self.view addSubview:self.loadingPropeller];
[self.loadingPropeller startPropeller];
Problem is: Without any further code. The propeller is not rotating. So I was able to solve it by adding this code into my class implementing to rotating propeller spinner:
-(void)viewDidAppear:(BOOL)animated
{
if(!self.loadingPropeller.hidden){
[self.loadingPropeller startPropeller];
}
}
But I don't like that too much. Isn't it possible to add some code within the Propeller class to solve this issue automatically, without having to add also code in every class in the viewDidAppear method?
The code that doesn't work does two essential things: adding the spinner to the view hierarchy and positioning it. My guess is that the failure is due to positioning it before layout has happened. Try this:
// in viewDidLoad of the containing vc...
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:CGRectZero andStyle:LoadingPropellerStyleNoBackground];
[self.view addSubview:self.loadingPropeller];
// within or after viewDidLayoutSubviews...
// (make sure to call super for any of these hooks)
self.loadingPropeller.frame = self.view.bounds;
self.loadingPropeller.center = self.view.center;
// within or after viewDidAppear (as you have it)...
[self.loadingPropeller startPropeller];
I've tried a lot of different options, and looked through about 15 stack answers and I just can not figure this out.
The code is basically trying to fade out, and then pop back, a view every time a tap happens. It works fine the first time, but will not work any subsequent times.
- (void)handleTap:(UIGestureRecognizer*)gestureRecognizer
{
self.view.transform = CGAffineTransformIdentity;
__block HelpScreenController* weakSelf = self;
[UIView animateWithDuration:10
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^(void) {
weakSelf.view.alpha = 0;
}
completion:^(BOOL finished) {
if (finished) {
weakSelf.view.alpha = 100.0f;
[weakSelf.view.layer removeAllAnimations];
[weakSelf.view setNeedsDisplay];
}
}];
}
It runs perfectly the first tap - it smoothly transitions from opaque to fully transparent over a 10 second period. Second+ tap(s) it wil sit there for 10 seconds, then go transparent for a heart beat then go back to fully opaque again.
How can I get it to animate smoothly every time?
Thanks in advance!
alpha values are between 0.0f and 1.0f. Setting the alpha value in the completion block to 1.0f instead of 100.0f should fix the problem.
Because values larger than 1.0f are all completely opaque, you will not see the transition from 100.0f to 1.0f (99% of your animation), so the effective duration of the transition from 1.0f to 0.0f would just be about 0.1 seconds instead of 10 (not exactly, because of the animation curve, but you get the idea).
You could just use a CABasicAnimation instead. Try out something like this:
- (void)handleTap:(UIGestureRecognizer*)gestureRecognizer
{
CALayer *viewLayer = self.view.layer;
[viewLayer removeAllAnimations];
CABasicAnimation *fader = [CABasicAnimation animationWithKeyPath:#"opacity"];
fader.fromValue = [NSNumber numberWithFloat:0.0];
fader.toValue = [NSNumber numberWithFloat:1.0];
fader.duration = 10;//change the duration and autoreverses option to fit with your look
fader.autoreverses = YES;
fader.repeatCount = 0;
[viewLayer addAnimation:fader forKey:#"fadeAnimation"];
}
Hope it helps!
I've written a method, sortAndSlideHandCards(), that moves 6 UIButtons. Each UIButton is moved to the same position. This is done via a for-each loop and the animateWithDuration method being called on each UIButton.
This method is called for a number of players at the same time. Currently the behaviour results in UIButtons from each player moving but only one at a time. No more than one UIButton can move at any time, as if each animation is waiting for whatever animation that is currently running to stop before attempting it's own animation, essentially the code is executed sequentially for each player/UIButton. I hoped threading would help me fix this.
when I added the threading code:
backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
sortAndSlideHandCardsGroup = dispatch_group_create();
for(Player* player in _playersArray) {
dispatch_group_async(sortAndSlideHandCardsGroup, backgroundQueue, ^(void) {
[player sortAndSlideHandCards];
});
dispatch_group_wait(sortAndSlideHandCardsGroup,DISPATCH_TIME_FOREVER);
I found that only the first UIButton animation is triggered for each player and that the code gets held up in the runloop "while" because "_animationEnd" never gets set as it would appear the second animation never gets going.
I can see the method launching in its own thread
- (void) sortAndSlideHandCards {
NSLog(#"PLAYER:sortAndSlideHandCards");
CGPoint newCenter;
Card* tempCard = nil;
int count = 1;
float duration = 0.2 / _speedMultiplyer;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for(Card *card in _handCards) { //move cards in hand to one postion in hand
if(count == 1) {
tempCard = [[Card alloc] init:_screenWidth:_screenHeight :[card getNumber] :[card getCardWeight] :[card getSuit] :[card getIsSpecial]];
[tempCard setImageSrc: _playerNumber :!_isPlayerOnPhone :count : true :_view: _isAI: [_handCards count]];
newCenter = [tempCard getButton].center;
}
_animationStillRunning = true;
if(![[DealViewController getCardsInPlayArray] containsObject:card] ) {
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{[card getButton].center = newCenter;} completion:^(BOOL finished){[self animationEnd];}];
while (_animationStillRunning){ //endAnimation will set _animationStillRunning to false when called
//stuck in here after first UIButton when threading code is in play
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} //endAnimation will set _animationStillRunning to false when called
}
count++;
}
}
When i comment out the threading code each UIButton (Card) will animate one after another.
With the threading code is in play the first UIButton will animate but during the second run through the for-loop, the code will be stuck in the while-loop, waiting for the animation to end. I'm guessing the second animation doesn't even start.
I also tried this for the threading code:
[player performSelectorInBackground:#selector(sortAndSlideHandCards) withObject:nil];
Same outcome
Anyone have any ideas why animateWithDuration doesn't like getting called in a loop when in a thread other than the main one?
You should just be able to kick off the animations you want from whatever UI action is performed. The UIView animateWith... methods return immediately, so you don't need to worry about waiting for them to complete.
If you have an unknown number of animations to kick off sequentially, use the delay parameter. Pseudocode:
NSTimeInterval delay = 0;
NSTimeInterval duration = 0.25;
for (UIView *view in viewsToAnimate)
{
[UIView animateWithDuration:duration delay:delay animations:^{ ... animations ...}];
delay += duration;
}
This will increase the delay for each successive animation, so it starts at the end of the previous one.
In my code asteroids coming toward to the ship,
I want to implement explosion animation if laser hits the asteroid.
Asteroid should run explosion animation and switch to invisible mode.
Without animation when target is hit, target successfully switches to invisible mode. Without setting object to invisible, animation runs great. When I put it together because of procedural code without seeing animation it quickly set object to invisible.
How can I both see the animation then set it to invisible mode. (targets aka asteroids are in various speeds some of them are too fast while others slow)
The idea to put target to invisible is prevent them hitting to ship.
I tried this question&answer cocos2d autoremove sprite after animation didnt work
for (CCSprite *asteroid in _asteroids)
{
if (!asteroid.visible) continue;
for (CCSprite *shipLaser in _shipLasers)
{
if (!shipLaser.visible) continue;
if (CGRectIntersectsRect(shipLaser.boundingBox, asteroid.boundingBox))
{
[[SimpleAudioEngine sharedEngine] playEffect:#"explosion_large.caf"];
//explosion zombie animation starts
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 12; ++i)
{
[walkAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"zombieexplodes%d.png", i]]];
}
CCAnimation *walkAnim = [CCAnimation
animationWithFrames:walkAnimFrames delay:0.1f];
_dieAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
[asteroid runAction:_dieAction];
//explosion zombie ends
[self addPoint];
//change meme to woohoo.png
[_ship setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: #"woohoo.png"]];
shipLaser.visible = NO;
[asteroid setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: #"zombieexplodes13.png"]];
//asteroid.visible=NO;
continue;
}
}
}
Use this style on calling:
CCAnimation *walkAnim = [CCAnimation
animationWithFrames:walkAnimFrames delay:0.1f];
id animate = [CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO];
id calFuncN = [CCCallFuncN actionWithTarget:self selector:#selector(explodeAnimDone:)];
id sequence = [CCSequence actions:animate, calFuncN,nil];
[asteroid runAction:sequence];
Disable sprite when your animation is done.
-(void)explodeAnimDone:(id)sender
{
CCNode *myNode = (CCNode*)sender;
myNode.visible = false;
}
Haven't done game dev in Cocoa2D, but when I made games like this I would have a separate explosion object that removed itself when its animation ended. So you should generate an explosion object and turn the asteroid invisible right away. If you can't get autoremove on animation finish to work, time the explosion and then set a timer on your explosion object to remove itself.
I noticed that you're turning asteroids invisible... you should remove them instead - they still take up memory when they are invisible.
Im having trouble animating a wheel using touch. Ive spent some time punching in different numbers values for duration, spin times and animation durations to get a smooth move using on thouchsMove, but every time a touch happens the wheel rotates and what seems to be happening is it jumps back to its original position. If any one can shed some light on this i would very much appreciate it.
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.delegate = self;
rotationAnimation.toValue = [NSNumber numberWithFloat: 2 * 1 * 45 ];
rotationAnimation.duration = 2;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[animatedImage.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
for those interested code below is whats needed to get the desired effect
CGAffineTransform transforms = CGAffineTransformConcat(animatedImage.transform,CGAffineTransformMakeRotation(M_PI/2));
animatedImage.transform = transforms;
If you are having the same problem follow this example, its spot on.
http://ericmcconkie.com/2010/03/trig-and-objective-c/