objective-cnstextfieldcabasicanimationnsanimationcontext

Shake animation for NSTextField


Based similar questions asked about UIView and UITextField shake animations, Ive implemented this code which should apply to NSTextFields. However the animation never runs. What is the problem here?

NSRect textFieldFrame = [textfield frame];

CGFloat centerX = textFieldFrame.origin.x;
CGFloat centerY = textFieldFrame.origin.y;

CABasicAnimation *animation = [[CABasicAnimation alloc] init];
animation.keyPath = @"position";
animation.duration = 0.07;
animation.repeatCount = 4;
animation.autoreverses = true;

NSPoint one = NSMakePoint(centerX-5, centerY);
NSPoint two = NSMakePoint(centerX+5, centerY);

animation.fromValue = [NSValue valueWithPoint:one];
animation.toValue = [NSValue valueWithPoint:two];

[textfield.layer addAnimation:animation forKey:@"position"];

Note: [[textfield animator] setFrameOrigin:one]; successfully moves the textfield


Solution

  • I ended up using recurring NSAnimationContext animation groups each one calling another on completion. If there is a better way to do this I am still looking. But incase anyone else needs this here is my solution

    NSRect textFieldFrame = [textfield frame];
    
    CGFloat centerX = textFieldFrame.origin.x;
    CGFloat centerY = textFieldFrame.origin.y;
    
    NSPoint origin = NSMakePoint(centerX, centerY);
    NSPoint one = NSMakePoint(centerX-5, centerY);
    NSPoint two = NSMakePoint(centerX+5, centerY);
    
    
    [NSAnimationContext beginGrouping];
    [[NSAnimationContext currentContext] setCompletionHandler:^{
    
        [NSAnimationContext beginGrouping];
        [[NSAnimationContext currentContext] setCompletionHandler:^{
    
    
            [NSAnimationContext beginGrouping];
            [[NSAnimationContext currentContext] setCompletionHandler:^{
    
                [NSAnimationContext beginGrouping];
                [[NSAnimationContext currentContext] setCompletionHandler:^{
    
                    [[NSAnimationContext currentContext] setDuration:0.0175];
                    [[NSAnimationContext currentContext] setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
                    [[textfield animator] setFrameOrigin:origin];
    
                }];
    
                [[NSAnimationContext currentContext] setDuration:0.0175];
                [[NSAnimationContext currentContext] setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
                [[textfield animator] setFrameOrigin:two];
                [NSAnimationContext endGrouping];
    
            }];
    
            [[NSAnimationContext currentContext] setDuration:0.0175];
            [[NSAnimationContext currentContext] setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
            [[textfield animator] setFrameOrigin:one];
            [NSAnimationContext endGrouping];
        }];
    
        [[NSAnimationContext currentContext] setDuration:0.0175];
        [[NSAnimationContext currentContext] setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
        [[textfield animator] setFrameOrigin:two];
        [NSAnimationContext endGrouping];
    
    }];
    
    [[NSAnimationContext currentContext] setDuration:0.0175];
    [[NSAnimationContext currentContext] setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
    [[textfield animator] setFrameOrigin:one];
    [NSAnimationContext endGrouping];