Search code examples
iosswiftuikitcore-animationcgpath

Animating the drawing of letters with CGPaths and CAShapeLayers


I'm currently using this code to animate the drawing of a character:

var path = UIBezierPath()


    var unichars = [UniChar]("J".utf16)
    var glyphs = [CGGlyph](count: unichars.count, repeatedValue: 0)

    let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
    if gotGlyphs {
        let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)
         path = UIBezierPath(CGPath: cgpath!)

    }

creates a bezierpath from a character ("J" in this case). Get path to trace out a character in an iOS UIFont

Then I create a CAShapeLayer() and add an animation to it.

 let layer = CAShapeLayer()
   layer.position = //CGPoint
    layer.bounds = //CGRect()
    view.layer.addSublayer(layer)

    layer.path = path.CGPath

    layer.lineWidth = 5.0
    layer.strokeColor = UIColor.blackColor().CGColor
    layer.fillColor = UIColor.clearColor().CGColor
    layer.geometryFlipped = true

    layer.strokeStart = 0.0
    layer.strokeEnd = 1.0

    let anim = CABasicAnimation(keyPath: "strokeEnd")
    anim.duration = 8.0
    anim.fromValue = 0.0
    anim.toValue = 1.0

    layer.addAnimation(anim, forKey: nil)

The result is my chosen character being animated correctly. However when I add another path to path with .appendPath() the appended path gets added right onto the original path as you could expect. What should I do if I want to draw a letter where all characters have appropriate spacing etc.? Thank you for your time.


Solution

  • You can do this using translation on path (using CGAffineTransformMakeTranslation), since path doesn't have a "position", it is just set of points. But to make a translation every iteration of a character, we need a current width of a path - we can use CGPathGetBoundingBox() for this, which gets the box that the path will cover. So having everything we need, we can make an example. Make a path for whole word would be something along the lines:

    let word = "Test"
    let path = UIBezierPath()
    let spacing: CGFloat = 50
    var i: CGFloat = 0
    for letter in word.characters {
        let newPath = getPathForLetter(letter)
        let actualPathRect = CGPathGetBoundingBox(path.CGPath)
        let transform = CGAffineTransformMakeTranslation((CGRectGetWidth(actualPathRect) + min(i, 1)*spacing), 0)
        newPath.applyTransform(transform)
        path.appendPath(newPath)
        i++
    }
    

    Where function getPathForLetter() is just:

    func getPathForLetter(letter: Character) -> UIBezierPath {
        var path = UIBezierPath()
        let font = CTFontCreateWithName("HelveticaNeue", 64, nil)
        var unichars = [UniChar]("\(letter)".utf16)
        var glyphs = [CGGlyph](count: unichars.count, repeatedValue: 0)
    
        let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
        if gotGlyphs {
            let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)
            path = UIBezierPath(CGPath: cgpath!)
        }
    
        return path
    }