Skip to content

Instantly share code, notes, and snippets.

@JeOam
Created August 29, 2014 09:12
Show Gist options
  • Select an option

  • Save JeOam/c621e5790c2159947446 to your computer and use it in GitHub Desktop.

Select an option

Save JeOam/c621e5790c2159947446 to your computer and use it in GitHub Desktop.
Animating Text Drawing
// AnimatingTextDrawing.h
#import <UIKit/UIKit.h>
@interface AnimatingTextDrawing : UIViewController
@end
#import "AnimatingTextDrawing.h"
#import <CoreText/CoreText.h>
#import <QuartzCore/QuartzCore.h>
@interface AnimatingTextDrawing()

@property (nonatomic, retain) CALayer *animationLayer;
@property (nonatomic, retain) CAShapeLayer *pathShapeLayer;

@end

@implementation AnimatingTextDrawing

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.animationLayer = [CALayer layer];
    self.animationLayer.frame = self.view.frame;
    [self.view.layer addSublayer:self.animationLayer];
    
    [self setupPathShapeLayer];
    [self drawAnimationPositiveDirection];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)setupPathShapeLayer
{
    if (self.pathShapeLayer != nil) {
        [self.pathShapeLayer removeFromSuperlayer];
        [self.animationLayer removeFromSuperlayer];
        self.pathShapeLayer = nil;
        self.animationLayer = nil;
    }
    
    // Create path from text - See: http://www.codeproject.com/Articles/109729/Low-level-text-rendering
    // Animating the drawing of a CGPath with CAShapeLayer - See: http://oleb.net/blog/2010/12/animating-drawing-of-cgpath-with-cashapelayer/
    
    CGMutablePathRef letters = CGPathCreateMutable();
    
    UIFont *font = [UIFont fontWithName:@"STHeitiTC-Medium" size:100.0];
    NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:@"" attributes:attrsDictionary];
    
    // The CTLine opaque type represents a line of text. A CTLine object contains an array of glyph runs
    // CTLineRef: A reference to a line object.
    // CTLineCreateWithAttributedString(): reates a single immutable line object directly from an attributed string.
    CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attrString);
    
    // CTLineGetGlyphRuns(): Returns the array of glyph runs that make up the line object.
    CFArrayRef runArray = CTLineGetGlyphRuns(line);
//    NSLog(@"CFArrayGetCount(runArray) is %ld",CFArrayGetCount(runArray));
    for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++)
    {
        // Get FONT for this run
        CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
        CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), NSFontAttributeName);
        
//        NSLog(@"CTRunGetGlyphCount(run) is %ld", CTRunGetGlyphCount(run));
        // for each GLYPH in run
        for (CFIndex runGlyphIndex = 0; runGlyphIndex < CTRunGetGlyphCount(run); runGlyphIndex++)
        {
            // get Glyph & Glyph-data
            CFRange thisGlyphRange = CFRangeMake(runGlyphIndex, 1);
            CGGlyph glyph;
            CGPoint position;
            CTRunGetGlyphs(run, thisGlyphRange, &glyph);
            CTRunGetPositions(run, thisGlyphRange, &position);
            
            // Get PATH of outline
            {
                CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);
                CGAffineTransform t = CGAffineTransformMakeTranslation(position.x, position.y);
                CGPathAddPath(letters, &t, letter);
                CGPathRelease(letter);
            }
        }
    }
    CFRelease(line);
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointZero];
    [path appendPath:[UIBezierPath bezierPathWithCGPath:letters]];
    
    CGPathRelease(letters);
    
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.frame = self.animationLayer.bounds;
	pathLayer.bounds = CGPathGetBoundingBox(path.CGPath);
//    pathLayer.backgroundColor = [[UIColor yellowColor] CGColor];
    pathLayer.geometryFlipped = YES;
    pathLayer.path = path.CGPath;
    pathLayer.strokeColor = [[UIColor blackColor] CGColor];
    pathLayer.fillColor = nil;
    pathLayer.lineWidth = 3.0f;
    pathLayer.lineJoin = kCALineJoinBevel;
    
    [self.animationLayer addSublayer:pathLayer];
    
    self.pathShapeLayer = pathLayer;
}

// 正方向画字
- (void)drawAnimationPositiveDirection
{
    [self.pathShapeLayer removeAllAnimations];
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    pathAnimation.duration = 2.0;
    pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
    
    // 设置速度函数
    [pathAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    pathAnimation.delegate = self;
    [self.pathShapeLayer addAnimation:pathAnimation forKey:@"strokeEnd"];
}

// 反方向画字
- (void)drawAnimationReverseDirection
{
    [self.pathShapeLayer removeAllAnimations];
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    pathAnimation.duration = 2.0;
    pathAnimation.fromValue = [NSNumber numberWithFloat:1.0f];
    pathAnimation.toValue = [NSNumber numberWithFloat:0.3888f];
    [self.pathShapeLayer addAnimation:pathAnimation forKey:@"strokeStart"];
}

// 反方向消失字
- (void)disappearAnimationPositiveDirection
{
    [self.pathShapeLayer removeAllAnimations];
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    pathAnimation.duration = 2.0;
    pathAnimation.fromValue = [NSNumber numberWithFloat:1.0f];
    pathAnimation.toValue = [NSNumber numberWithFloat:0.0f];
    [self.pathShapeLayer addAnimation:pathAnimation forKey:@"strokeEnd"];
}

// 正方向消失字
- (void)disappearAnimationReverseDirection
{
    [self.pathShapeLayer removeAllAnimations];
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    pathAnimation.duration = 2.0;
    pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
    [self.pathShapeLayer addAnimation:pathAnimation forKey:@"strokeStart"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    NSLog(@"animationDidStop");
    [self.pathShapeLayer removeFromSuperlayer];
}
@end

Ref: Animating the drawing of a CGPath with CAShapeLayer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment