Search code examples
objective-cxcodecore-animation

removeAllAnimations not working on successively called animations


I'm trying to set up an image gallery type view where the image is nearly full screen, and the nav controller, toolbar, buttons (to move between images), and slider (to quickly move between images) all fade out after periods without interaction, and then return on a tap. What I have so far (which I'm sure isn't even close to the right way to do this, I'm something of a beginner) is this:

-(void)fadeOutViews{


     [self fadeOutView:rightButton];
     [self fadeOutView:leftButton];
     [self fadeOutView:mySlider];
     mySlider.enabled = NO;
     [self fadeOutView:myToolbar];
     [self fadeOutView:self.navigationController.navigationBar];

}



-(void)fadeOutView:(UIView *)view{

    view.alpha = 1;

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];

    [UIView setAnimationDelegate:self];

    [UIView setAnimationDuration:2];
    view.alpha = 0;

    [UIView commitAnimations];

}

-(void)stopFadeOut{

    [rightButton.layer removeAllAnimations];
    [leftButton.layer removeAllAnimations];
    [mySlider.layer removeAllAnimations];
    [myToolbar.layer removeAllAnimations];
    [self.navigationController.navigationBar.layer removeAllAnimations];

}

-(void)resetToInitialConfigurationWithDelay:(int)delay{

    if (buttonWasPressed){
        delay = 4;
        buttonWasPressed = NO;
    }

    rightButton.alpha = 1;
    leftButton.alpha = 1;
    mySlider.alpha = 1;
    myToolbar.alpha = 1;
    self.navigationController.navigationBar.alpha = 1;

    mySlider.enabled = YES;

    [self stopFadeOut];

    [self performSelector:@selector(fadeOutViews) withObject:nil afterDelay:delay];
}

So, the theory is, you reset to the initial state (the delay is because images fade in and out when using the buttons to advance, so there needs to be more time in before fading after a button press, or else the fading started immediately after the new image loaded. This resets everything to how it started out, and begins the process of fading again. And stopFadeOut removes all the animations if something occurs that should stop the fading process. So, for example, if a tap occurs:

- (IBAction)tapOccurs:(id)sender {
    [self stopFadeOut];
    [self resetToInitialConfigurationWithDelay:2];
}

Any previous animations are stopped, and then the process is restarted. Or at least that's the theory. In practice, if, say, there are several taps in quick succession, the faded views will start to fade briefly, and the reset, over and over again, so that it looks like they are flashing, until they finally fade out completely. I thought that perhaps the issue was the the animations were delayed, but the removeAllAnimation calls were not, so I replaced [self stopFadeOut]; with [self performSelector:@selector(stopFadeOut) withObject:nil afterDelay:2];

but the results were the same. The behavior is EXACTLY the same if stopFadeOut is never called, so the only conclusion I can draw is that for whatever reason, the removeAllAnimations calls aren't working. Any ideas?


Solution

  • What is happening

    It sounds to me like the reset method is called multiple times before the previous run finished. You could easily verify this by adding log-statements to both the tap and reset method and count the number of logs for each method and watch what happens when you tap multiple times in a row.

    I've tried to illustrate the problem with drawing below.

    T = tapOccurs:
    O = fadeOutViews:
    --- = wait between T & O
    
    Normal single tap  
    T-----O    T-----O      
    ---------------------> time
    
    Multiple taps in a row
    T-----O 
      T-----O
          T-----O
    ---------------------> time
    
    What it sound like you are trying to do
    T-
      T---
          T-----O
    ---------------------> time
    

    Every time fadeOutViews: (called O in the illustration) gets called the view will fade out. Looking at your fadeOutView: implementation this means that the opacity will jump to 1 and then fade slowly to 0, thus it looks like they are flashing an equal number of times to the number of taps until finally starting over.

    How you can prevent this

    You could do a number of things to stop this from happening. One thing would be to cancel all the scheduled reset methods by calling something like cancelPerformSelectorsWithTarget: or cancelPreviousPerformRequestsWithTarget:selector:object:.