Search code examples
macoscore-animationcalayerquartz-graphicscaanimation

Trying to slide text with CAAnimation and CATextLayer



I'm trying to slide text in a view with a CATextLayer, but it's clearly not working:

The function animationDidStop:finished: gets called one time but with NO as flag value.

Code (NSView Subclass):

#import "AppStatusItemView.h"

#define DEFAULT_FRAME (NSMakeRect(0 , 0 , 80 , 20))

/* ============================================================================
 MARK: -
 MARK: Private Interface
 =========================================================================== */
@interface AppStatusItemView (Private)
- (void)updateLayer;
- (CATextLayer *)makeTextLayer;
- (CABasicAnimation *)makeMoveAnimation;
@end

/* ============================================================================
 MARK: -
 MARK: Public Implementation
 =========================================================================== */
@implementation AppStatusItemView
/* MARK: Init */
- (id)initWithText:(NSString *)pS {
    self = [self initWithFrame:DEFAULT_FRAME];
    if(self != nil) {
        _text = [pS retain];

        /* initialize view */
        [self updateLayer];
    }
    return self;
}

- (void)dealloc {
    [_text release];
    [_textLayer release];
    [super dealloc];
}

/* MARK: Properties */ 
@synthesize text=_text;
- (void)setText:(NSString *)pT {
    if(![_text isEqualToString:pT]) {
        [_text release];
        _text = [pT retain];

        [self updateLayer];
    }
}

/* MARK: Animation Delegate */
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    if(flag) {
        CABasicAnimation *myAnimation;
        myAnimation = [self makeMoveAnimation];
        [myAnimation setDelegate:self];
        [_textLayer addAnimation:myAnimation forKey:@"slide"];
    }
}
@end

/* ============================================================================
 MARK: -
 MARK: Private Implementation
 =========================================================================== */
@implementation AppStatusItemView (Private)
- (void)updateLayer {
    CABasicAnimation *myAnimation;
    CATextLayer *myLayer;

    myLayer = [self makeTextLayer];
    myAnimation = [self makeMoveAnimation];

    [_textLayer removeAllAnimations];
    [_textLayer removeFromSuperlayer];

    [_textLayer release];
    _textLayer = [myLayer retain];
    [[self layer] addSublayer:_textLayer];

    [myAnimation setDelegate:self];
    [_textLayer addAnimation:myAnimation forKey:@"slide"];
}

- (CATextLayer *)makeTextLayer {
    CATextLayer *layer;
    NSString *myString;
    CGRect myFrame;
    CGPoint myPosition;

    myString = _text;
    layer = [[CATextLayer layer] retain];
    [layer setString:myString];
    myFrame.origin = CGPointZero;
    myFrame.size = NSSizeToCGSize([myString sizeWithAttributes:nil]);
    [layer setFrame:myFrame];

    myPosition.y = 1.0;
    myPosition.x = [self bounds].size.width + 2.0;
    [layer setPosition:myPosition];

    return [layer autorelease];
}

- (CABasicAnimation *)makeMoveAnimation {
    CABasicAnimation *animation;
    CGPoint myPoint;

    myPoint.y = 1.0;
    animation = [CABasicAnimation animationWithKeyPath:@"position"];
    myPoint.x = [self bounds].size.width + 1.0;
    [animation setFromValue:[NSValue valueWithPoint:myPoint]];
    myPoint.x = -[_textLayer bounds].size.width - 1.0;
    [animation setToValue:[NSValue valueWithPoint:myPoint]];
    [animation setTimingFunction:
     [CAMediaTimingFunction 
      functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];

    [animation setDuration:5.0];

    return animation;
}
@end

Thank you in advance,
ief2


Solution

  • I've changed the code, but didn't debug the previous code.

    New implementation, if someone would like to see it:

    #import "AppStatusItemView.h"
    
    #define DEFAULT_FRAME (NSMakeRect(0 , 0 , 140 , 20))
    #define SLIDE_SPEED (30.0) /* points per second */
    
    /* ============================================================================
     MARK: -
     MARK: Private Interface
     =========================================================================== */
    @interface AppStatusItemView (Private)
    - (void)slide;
    - (NSTimeInterval)intervalWithWidth:(CGFloat)w speed:(CGFloat)s;
    @end
    
    /* ============================================================================
     MARK: -
     MARK: Public Implementation
     =========================================================================== */
    @implementation AppStatusItemView
    /* MARK: Init */
    - (id)initWithText:(NSString *)pS {
        self = [self initWithFrame:DEFAULT_FRAME];
        if(self != nil) {
            _text = [pS retain];
    
            /* Create root layer */
            {
                //CGColorRef myColor = CGColorCreateGenericRGB(0.5 , 0.0 , 0.0 , 0.5);
                _rootLayer = [[CAScrollLayer layer] retain];
                [_rootLayer setFrame:NSRectToCGRect([self bounds])];
                //[_rootLayer setBackgroundColor:myColor];
                [self setWantsLayer:YES];
                [self setLayer:_rootLayer];
                //CGColorRelease(myColor);
            }
    
            /* Create text layer */
            {
                CGColorRef myColor = CGColorCreateGenericRGB(0.0 , 0.0 , 0.0 , 1.0);
                _textLayer = [[CATextLayer layer] retain];
                [_textLayer setFontSize:12.0];
                [_textLayer setForegroundColor:myColor];
                [self slide];
                CGColorRelease(myColor);
            }
    
        }
        return self;
    }
    
    - (void)dealloc {
        [_text release];
        [_textLayer release];
        [_rootLayer release];
        [super dealloc];
    }
    
    /* MARK: Properties */ 
    @synthesize text=_text;
    - (void)setText:(NSString *)pT {
        if(![_text isEqualToString:pT]) {
            [_text release];
            _text = [pT retain];
        }
    }
    
    /* MARK: Animation Delegate */
    - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
        if(flag) {
            [self slide];
        }
    }
    @end
    
    /* ============================================================================
     MARK: -
     MARK: Private Implementation
     =========================================================================== */
    @implementation AppStatusItemView (Private)
    - (void)slide {
        NSRect startFrame, endFrame;
        NSPoint startPosititon, endPosition;
        NSSize textSize;
    
        [_textLayer removeFromSuperlayer];
    
        /* Set start frame and position */
        {
            NSDictionary *attributes;
            NSFont *font;
            CGFloat h;
    
            font = [NSFont fontWithName:@"Helvetica" 
                                   size:12.0];
            attributes = [NSDictionary dictionaryWithObject:font 
                                                     forKey:NSFontAttributeName];
            textSize = [_text sizeWithAttributes:attributes];
    
            h = ([self bounds].size.height - textSize.height) / 2.0;
            startFrame = NSMakeRect([self bounds].size.width,
                                    h, textSize.width , textSize.height);
            startPosititon.x = [self bounds].size.width + textSize.width / 2.0;
            startPosititon.y = [self bounds].size.height / 2.0;
    
            [_textLayer setString:_text];
            [_textLayer setFrame:NSRectToCGRect(startFrame)];
            [_textLayer setPosition:NSPointToCGPoint(startPosititon)];
        }
    
        /* Slide to end position */
        {
            CGFloat h;
            CABasicAnimation *animation;
    
            h = ([self bounds].size.height - textSize.height) / 2.0;
            endFrame = NSMakeRect(0.0 - textSize.width, h, 
                                  textSize.width, 
                                  textSize.height);
            endPosition.x = -textSize.width / 2.0;
            endPosition.y = [self bounds].size.height / 2.0;
    
            animation = [CABasicAnimation animationWithKeyPath:@"position"];
            [animation setFromValue:
             [NSValue valueWithPoint:startPosititon]];
            [animation setToValue:
             [NSValue valueWithPoint:endPosition]];
            [animation setDelegate:self];
            [animation setDuration:
             [self intervalWithWidth:textSize.width speed:SLIDE_SPEED]];
            [animation setTimingFunction:
             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
    
            [_textLayer addAnimation:animation forKey:@"slide"];
    
            //p[_textLayer setFrame:NSRectToCGRect(endFrame)];
            [_textLayer setPosition:NSPointToCGPoint(endPosition)];
        }
    
        [_rootLayer addSublayer:_textLayer];
    }
    
    - (NSTimeInterval)intervalWithWidth:(CGFloat)w speed:(CGFloat)s {
        return (NSTimeInterval)(w / s);
    }
    @end