How do I create a delayed CAAnimation that can be canceled and / or canceled while playing?
I have a layer that I want to show when the user takes an action, and hides when it completes. The layer will then be shown again if the user takes the action again.
In order to prevent the UI from transitioning through animation, I want:
- that the fade animation only starts after one second (and is canceled if the user takes an action before it starts).
- the fadein animation to display the layer starts at the current fadeout opacity if the user takes action before the layer disappears.
What's the best way to do this?
I tried this, but it doesn't work (a lot of layer blink noise):
- (void)hideHintLayer:(bool)hide
{
if(hide)
{
CABasicAnimation *animation = [CABasicAnimation animation];
animation.beginTime = CACurrentMediaTime() + 1.0f;
animation.duration = 1.0f;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.keyPath = @"opacity";
animation.fromValue = @(1.0f);
animation.toValue = @(0.0f);
[layer addAnimation:animation forKey:nil];
}
else
{
layer.opacity = 1.0f;
}
}
source to share
If you want to stop the animation, you can simply do
[layer removeAllAnimations];
If you want to know the current alpha
while the view is being animated hiding (so you can undo the animation starting from the desired location, you can do:
CALayer *presentationLayer = layer.presentationLayer;
CGFloat startingAlpha = presentationLayer.opacity;
You can then set the alpha to go from startingAlpha
at 1.0 to animate the invisible without flickering on the screen.
You can do the actual animation using block based animation, or I think you could use CABasicAnimation
, although I'm not sure why you did this.
So, for example, you can do something like the following (in my example I have a show button). I am using block animations, but I suspect this will work great for CABasicAnimation
:
- (IBAction)onPressShowButton:(id)sender
{
[self showAndScheduleHide];
}
- (void)showAndScheduleHide
{
[UIView animateWithDuration:1.0
animations:^{
self.containerView.alpha = 1.0;
}
completion:^(BOOL finished) {
[self scheduleHide];
}];
}
- (void)show
{
[UIView animateWithDuration:1.0
animations:^{
self.containerView.alpha = 1.0;
}
completion:nil];
}
- (void)scheduleHide
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(startToHide)
userInfo:nil
repeats:NO];
}
- (void)startToHide
{
self.timer = nil;
self.hiding = YES;
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.containerView.alpha = 0.0;
}
completion:^(BOOL finished) {
self.hiding = NO;
}];
}
Then you can use some utility method to modify it or redistribute the hidden object:
- (void)reverseAndPauseHide
{
// if we have a "hide" scheduled, then cancel that
if (self.timer)
{
[self.timer invalidate];
self.timer = nil;
}
// if we have a hide in progress, then reverse it
if (self.hiding)
{
[self.containerView.layer removeAllAnimations];
CALayer *layer = self.containerView.layer.presentationLayer;
CGFloat currentAlpha = layer.opacity;
self.containerView.alpha = currentAlpha;
[self show];
}
}
The question then becomes when you know you can call it reverseAndPauseHide
and when scheduleHide
again. So, for example, you can handle strokes:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self reverseAndPauseHide];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self scheduleHide];
}
source to share