Search code examples
cocoaanimationcore-animationmouseevent

NSView Does Not Respond To Mouse Events After CABasicAnimation


I have a pageLeft and pageRight animation that animates the position and alpha of 2 views, making a total of 4 simultaneous CABasicAnimations:

  1. Move next page into the view
  2. Move current page out of the view
  3. Fade in next page
  4. Fade out current page

Everything works well with the animations and setup. this question is about mouse events after the page animation. I have found that after the animation runs, the nextView does not receive mouseDown: events. If the animation is not run because the shouldNotAnimate block is run below instead, the nextView does receive mouseDown: events.

This means that something about the way I have setup my CABasicAnimations and CATransaction is causing this. I am stumped as to what it could be. Any ideas?


Animation Code:

BOOL forward = currentIndex < index;

if (shouldNotAnimate) {
    // handles swapping pages faster than the animation duration
    [self.sourceViewContainer setSubviews:[NSArray array]];

    [nextView.layer removeAllAnimations];
    [nextView setFrame:self.sourceViewContainer.bounds];
    [nextView.layer setPosition:self.sourceViewContainer.bounds.origin];
    [nextView.layer setOpacity:1.0];
    [self.sourceViewContainer addSubview:nextView];

    [currentView removeFromSuperview];      
    [currentView.layer removeAllAnimations];
    [currentView setFrame:self.sourceViewContainer.bounds];
    [currentView.layer setPosition:self.sourceViewContainer.bounds.origin];
    [currentView.layer setOpacity:1.0];

    self.animationsRunning = NO;

} else {

    self.animationsRunning = YES;

    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        self.animationsRunning = NO;
    }];

    // Setup incoming push animation
    [nextView setFrame:CGRectMake(self.sourceViewContainer.bounds.size.width * (forward ? 1 : -1), 0, self.sourceViewContainer.bounds.size.width, self.sourceViewContainer.bounds.size.width)];
    [self.sourceViewContainer addSubview:nextView];
    CABasicAnimation *incomingAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    incomingAnimation.fromValue = [NSValue valueWithPoint:nextView.frame.origin];
    incomingAnimation.toValue = [NSValue valueWithPoint:self.sourceViewContainer.bounds.origin];
    incomingAnimation.duration = PANEL_PAGE_SWIPE_ANIMATION_DURATION;
    incomingAnimation.removedOnCompletion = NO;
    incomingAnimation.fillMode = kCAFillModeForwards;
    incomingAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    incomingAnimation.completion = ^(BOOL finished) {
        [nextView.layer setPosition:self.sourceViewContainer.bounds.origin];
        [nextView.layer removeAnimationForKey:@"incoming"];
    };

    [nextView.layer addAnimation:incomingAnimation forKey:@"incoming"];


    // Setup outgoing push animation
    CGRect offscreen = CGRectMake(self.sourceViewContainer.bounds.size.width * (forward ? -1 : 1), 0, self.sourceViewContainer.bounds.size.width, self.sourceViewContainer.bounds.size.height);
    CABasicAnimation *outgoingAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    outgoingAnimation.fromValue = [NSValue valueWithPoint:self.sourceViewContainer.bounds.origin];
    outgoingAnimation.toValue = [NSValue valueWithPoint:offscreen.origin];
    outgoingAnimation.duration = PANEL_PAGE_SWIPE_ANIMATION_DURATION;
    outgoingAnimation.removedOnCompletion = NO;
    outgoingAnimation.fillMode = kCAFillModeForwards;
    outgoingAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    outgoingAnimation.completion = ^(BOOL finished) {
        [currentView removeFromSuperview];
        [currentView.layer setPosition:self.sourceViewContainer.bounds.origin];
        [currentView.layer removeAnimationForKey:@"outgoing"];

    };
    [currentView.layer addAnimation:outgoingAnimation forKey:@"outgoing"];


    // Setup incoming alpha animation
    CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeInAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    fadeInAnimation.toValue = [NSNumber numberWithFloat:1.0f];
    fadeInAnimation.duration = PANEL_PAGE_SWIPE_ANIMATION_DURATION;
    fadeInAnimation.removedOnCompletion = NO;
    fadeInAnimation.fillMode = kCAFillModeForwards;
    fadeInAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    fadeInAnimation.completion = ^(BOOL finished) {
        [nextView.layer setOpacity:1.0f];
        [nextView.layer removeAnimationForKey:@"fadeIn"];
    };
    [nextView.layer addAnimation:fadeInAnimation forKey:@"fadeIn"];


    // Setup outgoing alpha animation
    CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeOutAnimation.fromValue = [NSNumber numberWithFloat:1.0f];
    fadeOutAnimation.toValue = [NSNumber numberWithFloat:0.0f];
    fadeOutAnimation.duration = PANEL_PAGE_SWIPE_ANIMATION_DURATION;
    fadeOutAnimation.removedOnCompletion = NO;
    fadeOutAnimation.fillMode = kCAFillModeForwards;
    fadeOutAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    fadeOutAnimation.completion = ^(BOOL finished) {
        [currentView removeFromSuperview];
        [currentView.layer setOpacity:1.0f];
        [currentView.layer removeAnimationForKey:@"fadeOut"];

    };
    [currentView.layer addAnimation:fadeOutAnimation forKey:@"fadeOut"];

    [CATransaction commit];

} // Run animations

Solution

I had to explicitly set the view frame in the completion block, even though I had done this immediately prior to the animation calls.

    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        self.animationsRunning = NO;
        [nextView setFrame:self.sourceViewContainer.bounds];
    }];

Solution

  • I think I know what's going on.

    CAAnimation objects don't actually change the underlying property that they animation. Instead, it applies the properties to a presentation layer, which is drawn instead of the regular layer. When you create an animation with removedOnCompletion=false, the presentation layer is left active after the animation is completed, but the underlying property still isn't changed. Plus, moving a view's layer doesn't move the view itself, so it won't respond to user interaction at it's new location.

    I'd suggest moving your views using UIView block animation (UIView animateWithDuration:completion: and it's relatives) instead of using CAAnimation objects. Those methods actually move the view to it's new position, so that it responds to user interaction at the final location once the animation is complete.

    You could do your move and alpha change all in one animation block.