Objective-C:How to pause during a loop so that the sequence can finish before moving onto the next iteration?

I am making a simon game in objective-c using sprite kit in Xcode. What I am trying to accomplish is to pick random number, add it to the sequence, fadeout, pick next random number... what happens when I run this code is that all the tiles in the sequence fadeout then back in at exactly the same time.

Here is my code:

-(void) sequence
{
    NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
    int randomNumber;

    SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
    SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
    SKAction *pulse = [SKAction sequence:@[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in
    CFTimeInterval startTime;
    float elapsedTime;

    for(int i = 0; i<level+1; i++)
    {
        startTime = CFAbsoluteTimeGetCurrent();
        randomNumber=[self getRandomNumber]; //random number is picked

        if(randomNumber==0)
        {
            sequenceArray[i]=blueBox;
        }
        else if(randomNumber==1)
        {
            sequenceArray[i]=redBox;
        }
        else if(randomNumber==2)
        {
            sequenceArray[i]=yellowBox;
        }
        else if(randomNumber==3)
        {
            sequenceArray[i]=greenBox;
        }

        [sequenceArray[i] runAction:[SKAction repeatAction:pulse count:1]]; //for each tile in the sequence the color fades
                                                                            //out then back in

        elapsedTime = CFAbsoluteTimeGetCurrent()-startTime; //trying to pause the loop so the sequence can finish before the
                                                            //next random number is picked

        while(elapsedTime<.5)
        {
            elapsedTime = CFAbsoluteTimeGetCurrent()-startTime;
            NSLog(@"elapsed time %f", elapsedTime);
        }
    }
}

Ok I have tried to implement some of the suggestions below. I still am running into errors, for example now the sequenceArray[index] at index=0 is out of bounds.

Here is my new code:

-(void)doLoop:(int)index
    limit:(int)limit
   sprite:(SKSpriteNode*) sprite
     body:(void(^)(int))body

{

body(index);
index++;
if(index<level+1)
{
    SKAction *loopAction;
    [self runAction:loopAction completion:^{
    [self doLoop:index limit:limit sprite:sprite body:body];
    }];
}

}

-(void) sequence
{
    NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];
    int index = 0;

SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
SKAction *pulse = [SKAction sequence:@[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in


[self doLoop:0 limit:level+1 sprite:sequenceArray[index] body:^(int index){

    int randomNumber=[self getRandomNumber]; //random number is picked

    if(randomNumber==0)
    {
        sequenceArray[index]=blueBox;
    }
    else if(randomNumber==1)
    {
        sequenceArray[index]=redBox;
    }
    else if(randomNumber==2)
    {
        sequenceArray[index]=yellowBox;
    }
    else if(randomNumber==3)
    {
        sequenceArray[index]=greenBox;
    }

    [sequenceArray[index] runAction:pulse]; //for each tile in the sequence the color fades
                                                                        //out then back in

    NSLog(@"fadeout");
}];

}

Answers


Your attempt to delay your loop with a while statement is busy waiting and if this code is executing on your main thread provides no opportunity for your application to process events during the wait. This is the root cause of your problem.

What you need to do is replace your loop with a sequence of actions, each of which performs the work of one iteration, followed by completion blocks, where each completion block performs the "rest" of the loop. One way to do this is shown in this answer - in that case the need was for a simple delay and so the continuation of the "loop" is scheduled using dispatch_after().

In you case you can replace your calls to runAction: with calls to runAction:completion: to schedule the continuation of your "loop".

HTH

Addendum

Your attempt is a bit mixed up. Here is some untested code type directly into the answer - expect errors!

- (void) doLoop:(int)index
          limit:(int)limit
  sequenceArray:(NSMutableArray *)sequenceArray
         action:(SKAction *)pulse
{
   switch ([self getRandomNumber])
   {
      case 0:
         sequenceArray[index] = blueBox; break;
      case 1:
         sequenceArray[index] = redBox; break;
      case 2:
         sequenceArray[index] = yellowBox; break;
      case 3:
         sequenceArray[index] = greenBox; break;
   }

   [sequenceArray[index] runAction:[SKAction repeatAction:pulse count:1]
                        completion:^{
                                       // do next "iteration" if there is one
                                       int next = index + 1;
                                       if (next < limit)
                                          [self doLoop:next
                                                 limit:limit
                                         sequenceArray:sequenceArray
                                                action:pulse];
                                    }];

}

- (void) chainedSequence
{
   NSMutableArray *sequenceArray = [[NSMutableArray alloc] initWithCapacity:50];

   SKAction *fadeOut = [SKAction fadeOutWithDuration: 1]; //color of tile fades out
   SKAction *fadeIn = [SKAction fadeInWithDuration: 1]; //color of tile fades in
   SKAction *pulse = [SKAction sequence:@[fadeOut,fadeIn]]; //holds a sequence so tile fades out then fades in

   [self doLoop:0 limit:(level+1) sequenceArray:sequenceArray action:pulse];
}

Make sure you understand what it is doing before using it! The previously referenced answer provides more detail.

The above code passes around and fills up your sequenceArray, however you never use this in the code you provide other than for temporary storage within the method. Do you really need this array? If so remember that it contents will not be completely valid until the after the last "iteration" even though the call to doLoop: in chainedSequence will return before even the first action is complete.


It looks like you're calling this method on the main thread (the same thread which manages all of your application's view drawing). That means that you are queuing up all of your actions and then running them all at one. Your while delay slows down the entire loop but never yields control for these animations to run. None of your view changes will appear until your sequence method returns and the current event loop is able to finish.

Instead of waiting for animations to finish that while is delaying them from starting.

Look at creating an SKAction sequence which exists for exactly this sort of scenario; when you want a number of SKActions to fire sequentially. By adding all of your actions to a sequence and then starting the sequence you save yourself from needing to either run a bunch of callback blocks on the main thread or synchronize some background thread selecting blocks with the main thread animating those selections.


Need Your Help

How to access splitViewItems by item name in Swift + Storyboards

storyboard swift2 xcode7 nssplitviewcontroller

I'm currently using a NSSplitViewController that has a two child split view items (one is a View Controller and one is another Split View Controller as shown:

HTTPServletRequest equivalent in Apache CXF

java spring cxf

So I wanted to know my web service's client's locale or ip etc.. How do I get it?