How to run two methods in sequence at time delay? - objective-c

[self goToStage:currentStage];
[actor deathAnimation];
I have these two methods. I want to call it one by one in sequence. When one is completed after second one is started.
//this method used in nsobject subclass
-(void)deathAnimation {
//Play death animation
}
//this method in cclayer subclass
-(void)goToStage:(int)stage {
//changing scene
}
IN my tick function at particular event i want to call in seqeuence
I use following code but it not working
[CCSequence actions:[CCCallFunc actionWithTarget:actor selector:#selector(deathAnimation)], [CCDelayTime actionWithDuration:4.0], [CCCallFuncN actionWithTarget:self selector:#selector(goToStage:)], nil];
Now what i do? Please tell me.. Is there something wrong?

You can use CCDelayTime, that is used to give a delay, and CCCallBlock,to pass a method to be invoked, like this:
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:1],
[CCCallBlock actionWithBlock:^{[self goToStage:currentStage];}],
[CCDelayTime actionWithDuration:1],
[CCCallBlock actionWithBlock:^{[actor deathAnimation}], nil]];

Define a block in you inteface as e.g.
typedef void(^MyCompletion)(void);
edit you deathAnimation to take a block parameter as
- (void)deathAnimationWithCompletion:(MyCompletion)finish {
//..death animation
//...
//when animation finishes
finish(); // This will call your completion block
}
Call this method as
[self deathAnimationWithCompletion:^{
[self goToStage:2];
}];
You can read up on blocks at Ray Wenderlichs fantastic blog.
Hope it helps!
EDIT DUE TO COMMENT
In cocos2d I think you can also make a sequence like this
id aFuncCall = [CCCallFunc actionWithTarget:self selector:#selector(deathAnimation)];
id antoherFuncCall = [CCCallFunc actionWithTarget:self selector:#selector(goToSecondStange:)];
CCSequence *sequence = [CCSequence actions:aFuncCall,anotherFuncCall, nil];
[self runAction:sequence];
But my cocos2d programming skills are bit outdated so not sure if this works...

You can call
[actor deathAnimation];
at the end of the goToStage function, that way it will execute everything in goToStage before calling deathAnimation.
You could put a delay timer in the goToStage if you require it to wait for a specific amount of time.

Related

why does implementing callbacks (via target/selector or blocks) to my CCSequence crash my app?

I am trying to remove a sprite off of my main game layer after the sprite itself is done animating... To do achieve this, I first attempted to pass a block into the sprite object's CCSequence like this:
#Game.m
// some method
[self.spriteMan zoomAwayWithBlock:{[self destroySpriteMan];}];
[self createNewSpriteMan];
}
-(void)destroySpriteMan {
[self removeChild:self.spriteMan cleanup:YES];
}
#SpriteMan.m
-(void)zoomAwayWithBlock:(void(^)())block {
[self runAction:[CCSequence actions: [CCScaleTo actionWithDuration:2.0f scale:1.0f],
[CCCallFuncN actionWithBlock:block],
nil]];
}
I wondered if somehow the binding of self.spriteMan was getting messed up do to [self createNewSpriteMan] being called prior to the animation completing.... So I stored spriteMan in a tempSpriteMan variable prior to calling it, and attempting to removeChild on tempSpriteMan..... both results in a crash immediately.
I then rewrote this to use selectors and targets:
#game.m
[self.spriteMan zoomAwayWithSelector:#selector(destroySpriteMan:) target:self];
-(void)destroySpriteMan:(SpriteMan *)spriteMan {
[self removeChild:spriteMan cleanup:YES];
}
#SpriteMan.m
-(void)zoomAwayWithSelector:(SEL)sel target:(id)target {
[self runAction:[CCSequence actions: [CCScaleTo actionWithDuration:2.0f scale:1.0f],
[CCCallFuncN actionWithTarget:target selector:sel],
nil]];
}
same result.. crash every time...... What am I doing wrong?
As Aroth pointed out, the answer lies in this topic:
http://cocos2d-iphone.org/forum/topic/6818
It shows a solution to the problem, putting this into the array of actions solved the problem for me:
[CCCallFuncO actionWithTarget:self selector:#selector(removeFromParentAndCleanup:) object:[CCNode node]].
You can try to add category to CCNode with method
- (void) removeFromParentWithCleanupYes
{
[self removeFromParentWithCleanup:YES];
}
then just make sequences like this
id sequence = [CCSequence actions: action1,
action2,
...,
[CCCallFunc actionWithTarget: target selector:#selector(removeFromParentWithCleanupYes)],
nil];
[target runAction: sequence];
it is not very elegant, but it should work.
Or you can create your own simple action, which will remove it's target from parent.

This animation is not functioning in this method

I'm having a lot of trouble figuring this one out. This method is imported from another class into my viewController.m It works fine if I copy the code into an IBAction in the same file. And the "test midi" is logging when it is supposed to. So the IBOutlets and animation code seem OK but for some reason this method does not do what it is supposed to
- (void) source:(theSource*)data dataReceived:(const dataList *)theList
{
led.animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"led-highlighted.png"],[UIImage imageNamed:#"led-passive.png"],nil];
led.animationDuration = 0.3;
led.animationRepeatCount = 20;
[led startAnimating];
NSLog(#"test");}
it also doesn't work if I simply put this to swap the image. The method is being called because it is logging to console.
midiLed.image = [UIImage imageNamed:# "led-highlighted.png"];
The problem may lie in how you are calling this method. It should be on the main thread, and after calling it, your code should return to the run loop (i.e. your code should be "done") before changes to the user interface will have effect.
If this doesn't help you find it, please share the code calling this method.
What thread is -source:dataReceived: being called on? If you're using CoreMIDI, your MIDI-received function will get called on a separate, high-priority thread.
You should not touch the UI from a non-main thread, because UIKit isn't thread safe.
Here's one way to get the UI running on the main thread:
- (void) source:(theSource*)data dataReceived:(const dataList *)theList
{
dispatch_async(dispatch_get_main_queue(), ^{
led.animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"led-highlighted.png"],[UIImage imageNamed:#"led-passive.png"],nil];
led.animationDuration = 0.3;
led.animationRepeatCount = 20;
[led startAnimating];
NSLog(#"test");
});
}

Check if an action is currently running?

Is it possible to check if there are actions currently running in a CCNode class in Cocos2d? I'd like to know if a CCMoveBy is still running or not.
You can use [self numberOfRunningActions] on any CCNode. In your case, it sounds like you want to know if there are simply any actions running or not, so it's not a big deal to know the exact number beforehand.
We can easily check if specific actions run by using getActionByTag method and action.tag property.
There is no need to introduce the CCCallFuncN callbacks or counting numberOfRunningActions.
Example.
In our app it is important to let the jumpAction to be finished prior to executing another jump. To prevent triggering another jump during an already running jump action
the critical jump section of code is protected as follows:
#define JUMP_ACTION_TAG 1001
-(void)jump {
// check if the action with tag JUMP_ACTION_TAG is running:
CCAction *action = [sprite getActionByTag:JUMP_ACTION_TAG];
if(!action) // if action is not running execute the section below:
{
// create jumpAction:
CCJumpBy *jumpAction = [CCJumpBy actionWithDuration:jumpDuration position:ccp(0,0) height:jumpHeight jumps:1];
// assign tag JUMP_ACTION_TAG to the jumpAction:
jumpAction.tag = JUMP_ACTION_TAG;
[sprite runAction:jumpAction]; // run the action
}
}
You can always add a method to indicate when the method is finished, and then toggle some BOOL or something like that to indicate it is not running, and put a start method to toggle the BOOL to indicate it started:
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(-target.contentSize.width/2, actualY)];
id actionMoveDone = [CCCallFuncN actionWithTarget:self
selector:#selector(spriteMoveFinished:)];
id actionMoveStarted = [CCCallFuncN actionWithTarget:self
selector:#selector(spriteMoveStarted:)];
[target runAction:[CCSequence actions:actionMoveStarted, actionMove, actionMoveDone, nil]];
Modified from here.
In the two #selector methods:
-(void) spriteMoveStarted:(id)sender {
ccMoveByIsRunning = YES;
}
and:
-(void) spriteMoveFinished:(id)sender {
ccMoveByIsRunning = NO;
}
where ccmoveByIsRunning is the BOOL I'm referring to.
EDIT: As xus has pointed out, you should actually not do this and instead use [self numberOfRunningActions] as others have pointed out.

How can I make a method stall for a fixed amount of time?

I have an app that calls a sometimes-fast, sometimes-slow method. I know an upper bound for how long it will take (2 seconds). I'd like to set a timer to start when the method is called, run the code, but then not produce the output until 2 seconds has passed, no matter how long it actually takes. That way the user perceives the action as always taking the same amount of time. How can I implement this?
What I would like is something along the lines of this:
-(IBAction)doStuff {
// START A TIMER, LOOK BUSY
[activityIndicator startAnimating];
... real work happens here ...
... NSString *coolString gets assigned ...
// WHEN TIMER == 2 SECONDS, REVEAL COOLNESS
[activityIndicator stopAnimating];
[textField setText:coolString];
}
There are a couple of ways to delay an action in Cocoa. The easiest may be to use performSelector:withObject:afterDelay:. This method sets up a timer for you and calls the specified method when the time comes. It's an NSObject method, so your objects all get it for free.
The tricky part here is that the first method will block the main thread, so you need get it onto a background thread, and then get back to the main thread in order to update the UI. Here's a stab at it:
// Put the method which will take a while onto another thread
[self performSelectorInBackground:#selector(doWorkForUnknownTime)
withObject:nil];
// Delay the display for exactly two seconds, on the main thread
[self performSelector:#selector(displayResults)
withObject:nil
afterDelay:2.0];
- (void)doWorkForUnknownTime {
// results is an ivar
results = ...; // Perform calculations
}
- (void)displayResults {
if( !results ){
// Make sure that we really got results
[self performSelector:#selector(displayResults:)
withObject:nil
afterDelay:0.5];
return;
}
// Do the display!
}
The only other thing I can think of is to store the time that the "work" method is called in an NSDate, and check how long it took when you get the results. If it isn't two seconds yet, sleep the background thread, then call back to the main thread when you're done.
[self performSelectorInBackground:#selector(doWorkForUnknownTime:)
withObject:[NSDate date]];
- (void)doWorkForUnknownTime:(NSDate *)startTime {
// All threads must have an autorelease pool in place for Cocoa.
#autoreleasepool{
// This will take some time
NSString * results = ...; // Perform calculations
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startTime];
if( elapsedTime < 2.0 ){
// Okay to do this to wait since we're on a background thread,
// although not ideal; sleeping threads are kind of wasteful.
// Try not to do this a lot.
sleep(2.0 - elapsedTime);
}
// Don't forget to retain results on the main thread!
[self performSelectorOnMainThread:#selector(displayResults:)
withObject:results
waitUntilDone:YES];
// [results release]; // if necessary
}
}
[self performSelector:#selector(myfunc) withObject: afterDelay:];
should help.
-(IBAction)doStuff {
// START A TIMER, LOOK BUSY
[activityIndicator startAnimating];
... real work happens here ...
... NSString *coolString gets assigned ...
// WHEN TIMER == 2 SECONDS, REVEAL COOLNESS
[self performSelector:#selector(revealCoolnessWithString:) withObject:coolString afterDelay:2];
}
- (void)revealCoolnessWithString:(NSString *)coolString
{
[activityIndicator stopAnimating];
[textField setText:coolString];
}
Hope this helps

Confounding Cocoa problem — program hangs unless there’s an unrecognised method call

Bear with me, this one is hard to explain. I hope some hero out there knows what’s going on here. Some history needed;
One of my cocoa objects, “Ball” represents a small graphic. It only makes sense within a view. In some of the Ball’s methods, it asks the view to redraw. Most importantly, it asks the view to redraw whenever the Ball’s position parameter is set. This is achieved in the setter.
Here’s the mouthful, as suggested:
In View.m
- (void)mouseUp:(NSEvent *)theEvent {
if (![runnerPath isEmpty]) {
[walkPath removeAllPoints];
[walkPath appendBezierPath:runnerPath];
[runnerPath removeAllPoints];
[[self held] setStep:0];
[[self held] setPath:walkPath];
[NSTimer scheduledTimerWithTimeInterval:.01 target:[self held] selector:#selector(pace) userInfo:nil repeats:YES];
}
}
In Ball.m
- (void)pace {
CGFloat juice = 10;
BOOL loop = YES;
while (loop) {
if ([self step] == [[self path] elementCount]) {
if ([[self timer] isValid]) {
[[self timer] invalidate];
}
[[self path] removeAllPoints];
// #throw([NSException exceptionWithName:#"test" reason:#"reason" userInfo:nil]);
}
if (loop) {
CGFloat distance;
NSPoint stepPoint;
if ([[self path] elementCount] > 0) {
NSPoint returnPoints[2];
[[self path] elementAtIndex:[self step] associatedPoints:returnPoints];
stepPoint = returnPoints[0];
distance = pixelDistance([self position], stepPoint);
}
if (distance <= juice) {
[self setPosition:stepPoint];
if (distance < juice) {
juice -= distance;
loop = YES;
[self setStep:[self step]+1];
} else {
loop = NO;
}
} else {
NSPoint cutPoint = moveAlongBetween([self position], stepPoint, juice);
[self setPosition:cutPoint];
loop = NO;
}
}
}
}
could you also tell how you handle exceptions? since normally an unrecognized selector will end your program. Maybe you need an exception rather than an unrecognized selector. Try:
#throw([NSException exceptionWithName:#"test" reason:#"reason" userInfo:nil]);
If this would fix it as well, you're doing something after this code which freezes the app.
edit: thanks for the code update.
There's some weird stuff going on here! I'm not going to rewrite the whole thing, so here's some pointers:
first of all: you're looping inside some routine that is called from a timer loop. Is that intended? There is no way to pause execution within that while() loop, so it will happen in a blink anyway. You would need to keep some state information in the class. E.g. adding a loop counter every time pace is called.
second: if you start a timer, it will call your selector with the timer as an argument. So define the function as -(void)pace:(NSTimer*)timer, and use timer, not [self timer] (the latter will not be your timer anyway, if you don't assign it!)
third: you're firing 100 times a second. That is a lot, and presumably higher than the refresh rate of any device you're writing this for. I think 20/sec is enough.
fourth: to be sure, if you change it to -(void)pace:(NSTimer*)timer, don't forget to use #selector(pace:) (i.e. don't forget the :)
fix those things, and if it's still broken, update your question again and put in comment so we will know. Good luck!
Try calling
for (NSView *each in [self views]) {
...
}
I'm assuming that views is an array, so fast enumeration applies to it directly and there is no need to call allObjects.
A couple of other points.
Have you set a Global breakpoint of objc_exception_throw? This will apply to all Xcode projects and is so useful I'm surprised it isn't set by default.
You say you looked at the Console for errors. I take it, then, that you didn't set a breakpoint on the code and step into it to see exactly what is happening when your execution reaches that point? Have a look at the Xcode Debugging Guide