Swift: How to add points to a closed CGPath?

I’d like to make SKSpriteNodes to move along letter outlines. I have many letters but here’s one example:

enter image description here

  • How to make universal round draws (with lineDashPattern) with UIbezierPath on Swift
  • UIBezierPath in NSDictionary - is it possible?
  • Crazy rounded rect UIBezierPath behavior on iOS 7. What is the deal?
  • clip-masking uiview with CAShapeLayer and UIBezierPath
  • How to scale a CAShapeLayer
  • addArc(withCenter) closing path
  • I would like the sprite to follow the red line. I found this answer that mostly covers my problem: Get path to trace out a character in an iOS UIFont

    The answer comes with this good and working example code:

     let font = UIFont(name: "HelveticaNeue", size: 64)!
    
    
    var unichars = [UniChar]("P".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)!
        let path = UIBezierPath(CGPath: cgpath)
        print(path)
        XCPlaygroundPage.currentPage.captureValue(path, withIdentifier: "glyph \(glyphs[0])")
    }
    

    However I still run into a problem as my sprite doesn’t complete full path around the letter for all the letters but instead e.g. with “P” stops here (and started from the bottom):

    enter image description here

    I tried adding some points to the path like so:

    CGPathAddLineToPoint(path, nil, 0, 0)
    

    but the result doesn’t work probably because the added point is after the <Close> statement:

    <UIBezierPath: 0x7889ff70; <MoveTo {25.950001, 55.800003}>,
     <LineTo {25.950001, 95.100006}>,
     <LineTo {53.850002, 95.100006}>,
     <QuadCurveTo {71.625, 90.075005} - {66, 95.100006}>,
     <QuadCurveTo {77.25, 75.450005} - {77.25, 85.050003}>,
     <QuadCurveTo {71.625, 60.750004} - {77.25, 65.850006}>,
     <QuadCurveTo {53.850002, 55.800003} - {66, 55.650002}>,
     <Close>,
     <MoveTo {11.700001, 107.10001}>,
     <LineTo {11.700001, 0}>,
     <LineTo {25.950001, 0}>,
     <LineTo {25.950001, 43.800003}>,
     <LineTo {58.650002, 43.800003}>,
     <QuadCurveTo {83.175003, 52.050003} - {74.850006, 43.650002}>,
     <QuadCurveTo {91.5, 75.450005} - {91.5, 60.450001}>,
     <QuadCurveTo {83.175003, 98.775002} - {91.5, 90.450005}>,
     <QuadCurveTo {58.650002, 107.10001} - {74.850006, 107.10001}>,
     <Close>,
     <LineTo {0, 0}>
    

    Solutions Collect From Internet About “Swift: How to add points to a closed CGPath?”

    First of all you need a method to retrieve all elements from CGPath.

    I’ve translated to Swift this method (you find also an example to how to use it).

    As I can see here in your code, you need also to know what kinds of element compose your path, but CGPathElement work with UnsafeMutablePointer<CGPoint> (it can be not confortable), so you can modify my translation by creating a function with two output arrays like this:

    //MARK: - CGPath extensions
    extension CGPath {
        func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) {
                var arrayPoints : [CGPoint]! = [CGPoint]()
                var arrayTypes : [CGPathElementType]! = [CGPathElementType]()
                self.forEach { element in
                    switch (element.type) {
                    case CGPathElementType.MoveToPoint:
                        arrayPoints.append(element.points[0])
                        arrayTypes.append(element.type)
                    case .AddLineToPoint:
                        arrayPoints.append(element.points[0])
                        arrayTypes.append(element.type)
                    case .AddQuadCurveToPoint:
                        arrayPoints.append(element.points[0])
                        arrayPoints.append(element.points[1])
                        arrayTypes.append(element.type)
                        arrayTypes.append(element.type)
                    case .AddCurveToPoint:
                        arrayPoints.append(element.points[0])
                        arrayPoints.append(element.points[1])
                        arrayPoints.append(element.points[2])
                        arrayTypes.append(element.type)
                        arrayTypes.append(element.type)
                        arrayTypes.append(element.type)
                    default: break
                    }
                }
                return (arrayPoints,arrayTypes)
        }
    }
    

    After that you have a array of points and a specular array of type that explain what type is each point in our list. This time all closePath are removed.

    Now it’s time to re-create path ad HOC for you situation:

    func createNewPath(path:CGPath) -> UIBezierPath {
            let (points,types) = path.getPathElementsPointsAndTypes()
            if points.count <= 1 {
                return  UIBezierPath() // exit
            }
    
            let pathRef = UIBezierPath()
            var i = 0
            while i < points.count {
                switch (types[i]) {
                    case CGPathElementType.MoveToPoint:
                        pathRef.moveToPoint(CGPointMake(points[i].x,points[i].y))
                    case .AddLineToPoint:
                        pathRef.addLineToPoint(CGPointMake(points[i].x,points[i].y))
                    case .AddQuadCurveToPoint:
                        pathRef.addQuadCurveToPoint(CGPointMake(points[i].x,points[i].y), controlPoint: CGPointMake(points[i+1].x,points[i+1].y))
                        i += 1
                    case .AddCurveToPoint:
                        pathRef.addCurveToPoint(CGPointMake(points[i].x,points[i].y), controlPoint1: CGPointMake(points[i+1].x,points[i+1].y), controlPoint2: CGPointMake(points[i+2].x,points[i+2].y))
                        i += 2
                default: break
                }
                i += 1
            }
            //pathRef.closePath() if you want to add new elements dont uncomment this
            return pathRef
        }
    

    After launching this method, you will have your path cleaned by all closePath and ready to add new lines as you wish.