Search code examples
ioscadisplaylink

CADisplayLink can't stop after invalidated


I have two UIButton, the first button will trigger the CustomeView's - beginAnimation and the other one will trigger the - endAnimation. when I rapidly press these two button in turn, like begin -> end -> begin -> end -> begin -> end, I found that the CADisplayLink can't stop. What's more, the - rotate's fire rate is more than 60fps, became 60 -> 120 -> 180, just like there are more than one CADisplaylink in my main RunLoop, so is there anyway to fix it? And I need to keep the CADisplaylink running before the view's alpha come to zero, so I put the [self.displayLink invalidate]; in the completion block, maybe this will cause this problem?

@interface CustomeView : UIView
@end

@implementation CustomeView

- (void)beginAnimation // triggered by a UIButton
{
    [UIView animateWithDuration:0.5 animations:^{ self.alpha = 1.0; }];
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(rotate)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)endAnimation // triggered by another UIButton
{
    [UIView animateWithDuration:0.5 animations:^{ self.alpha = 0.0; } completion:^(BOOL finished) {
        [self.displayLink invalidate];
    }];
}

- (void)rotate
{
    // ....
}

Solution

  • If you call -beginAnimation before the completion block in -endAnimation has run -- that is, before the 0.5 second animation has finished -- you will overwrite the old self.displayLink with the new one. Afterwards, when the completion block runs, you will invalidate the new display link, not the old one.

    Use an intermediate variable to capture the value of self.displayLink that holds the display link that you want to invalidate. Also, for good measure, set self.displayLink to nil when you are done with it.

    - (void)beginAnimation // triggered by a UIButton
    {
        [UIView animateWithDuration:0.5 animations:^{ self.alpha = 1.0; }];
    
        if (self.displayLink != nil) {
            // You called -beginAnimation before you called -endAnimation.
            // I'm not sure if your code is doing this or not, but if it does,
            // you need to decide how to handle it.
        } else {
            // Make a new display link.
            self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(rotate)];
            [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        }
    }
    
    - (void)endAnimation // triggered by another UIButton
    {
        if (self.displayLink == nil) {
            // You called -endAnimation before -beginAnimation.
            // Again, you need to determine what, if anything,
            // to do in this case.
        } else {
            CADisplayLink oldDisplayLink = self.displayLink;
            self.displayLink = nil;
    
            [UIView animateWithDuration:0.5 animations:^{ self.alpha = 0.0; } completion:^(BOOL finished) {
                [oldDisplayLink invalidate];
            }];
        }
    }