1 Replies - 3041 Views - Last Post: 25 February 2015 - 12:46 PM

#1 gouldsc   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 12
  • Joined: 03-December 14

NSBezierPath to CGPathRef in Swift

Posted 25 February 2015 - 01:12 AM

I'm working on a CAShapeLayer in a Cocoa app. I'm trying to convert from an NSBezierPath to a CGPathRef. I've ported the Objective-C code in the documention to Swift. I was drawing a "splat" image using an NSBezierPath in the view's drawrect and it was working and drawing as expected. I removed that code and implemented the drawing as a CAShapeLayer using the same code that generated the correct NSBezierPath and then converted it to a CGPathRef using my extension. Now when it draws I get more of a starburst pattern with all lines passing through the center instead of a curvy perimeter shape that is expected (kind of like a sine wave around the circumference of a circle):

Image of the broken CGPathRef path

I've printed out the NSBezierPath and then the CGPathRef after being converted and I'm getting different values. I'm not sure if this is an indication of my bug or something to do with these values being calculated differently with NSBezierPath vs. Core Graphics. Here is are the values I printed out:

Path <0x618000121040>
  Bounds: {{-43.106679048632941, -206.26598233716513}, {1064.0629101876848, 996.166458522281}}
  Control point bounds: {{-43.106679048632941, -215.54617008066975}, {1086.0399792869732, 1024.7379140351486}}
    912.834872 307.031438 moveto
    920.666410 361.357898 989.988852 460.683911 967.049766 528.017164 curveto
    933.840173 590.922582 813.403322 615.855165 770.708862 646.253332 curveto
    731.900866 681.478394 724.786037 760.880548 655.711683 788.481860 curveto
    582.809548 803.259464 487.493534 697.937432 443.393531 688.102971 curveto
    399.213903 678.632630 176.793006 809.191744 131.911251 777.898616 curveto
    95.091170 737.427473 171.139054 507.009375 159.339185 472.144083 curveto
    145.105145 438.199752 -40.122240 395.556306 -43.106679 326.920564 curveto
    -38.034507 258.407462 173.461287 234.374823 205.966615 178.965366 curveto
    230.569965 119.623312 216.810754 13.335327 249.566700 -18.055620 curveto
    288.395969 -41.521158 396.218121 10.257299 439.206548 0.512085 curveto
    481.880067 -10.530780 557.861105 -215.546170 632.062781 -205.938693 curveto
    704.018407 -185.430636 698.731445 36.387668 728.801631 63.233741 curveto
    762.578511 85.234881 977.460930 36.181002 1017.714151 102.523873 curveto
    1042.933300 175.911152 912.741604 252.143474 912.834872 307.031438 curveto
    closepath
    912.834872 307.031438 moveto
<CGPath 0x6100000303e0>
Path <0x608000121d60>
  Bounds: {{67.463852803732493, -171.48619171527298}, {968.57641859705279, 995.59229064823171}}
  Control point bounds: {{67.463852803732493, -183.50241066074409}, {990.56724378377578, 1016.032466448921}}
    934.577063 350.376572 moveto
    937.009720 387.032884 1058.031097 486.802100 1032.506014 558.253738 curveto
    997.629504 625.636936 782.780920 623.860756 745.928936 646.779977 curveto
    712.657876 674.643859 700.846441 801.198333 636.622886 823.824376 curveto
    569.089088 832.530056 494.667973 636.692523 452.145282 621.429202 curveto
    407.316993 615.810250 187.201917 815.207571 136.896671 780.265299 curveto
    94.945086 735.637453 264.093734 501.822201 250.728633 463.728076 curveto
    229.627615 429.310997 69.614708 375.077726 67.463853 295.976682 curveto
    83.279669 218.443069 157.282894 198.798770 179.089961 163.063917 curveto
    194.907920 124.304143 145.063630 -57.967398 191.941599 -97.806318 curveto
    242.953546 -132.193305 378.628647 -43.229625 444.991191 -53.445783 curveto
    510.355716 -68.802737 590.530622 -183.502411 660.499176 -170.454097 curveto
    727.222944 -145.679192 702.172498 8.736577 735.707790 39.749182 curveto
    775.532672 62.118276 972.571713 22.452385 1006.566024 74.949073 curveto
    1031.465644 132.320882 939.633960 313.989338 934.577063 350.376572 curveto
    closepath
    934.577063 350.376572 moveto
<CGPath 0x610000031d00>


Here is Apple's Objective-C implementation of the conversion between an NSBezierPath and a CGPathRef

@implementation NSBezierPath (BezierPathQuartzUtilities)
// This method works only in OS X v10.2 and later.
- (CGPathRef)quartzPath
{
    int i, numElements;
 
    // Need to begin a path here.
    CGPathRef           immutablePath = NULL;
 
    // Then draw the path elements.
    numElements = [self elementCount];
    if (numElements > 0)
    {
        CGMutablePathRef    path = CGPathCreateMutable();
        NSPoint             points[3];
        BOOL                didClosePath = YES;
 
        for (i = 0; i < numElements; i++)
        {
            switch ([self elementAtIndex:i associatedPoints:points])
            {
                case NSMoveToBezierPathElement:
                    CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
                    break;
 
                case NSLineToBezierPathElement:
                    CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
                    didClosePath = NO;
                    break;
 
                case NSCurveToBezierPathElement:
                    CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
                                        points[1].x, points[1].y,
                                        points[2].x, points[2].y);
                    didClosePath = NO;
                    break;
 
                case NSClosePathBezierPathElement:
                    CGPathCloseSubpath(path);
                    didClosePath = YES;
                    break;
            }
        }
 
        // Be sure the path is closed or Quartz may not do valid hit detection.
        if (!didClosePath)
            CGPathCloseSubpath(path);
 
        immutablePath = CGPathCreateCopy(path);
        CGPathRelease(path);
    }
 
    return immutablePath;
}
@end


Here is my Swift port of the code:
extension NSBezierPath
{
    func convertToCGPath() -> CGPathRef?
    {
        let tElementCount = self.elementCount
        if tElementCount <= 0
        {
            return nil
        }
        
        var tPath = CGPathCreateMutable()
        var tPointsMArray = NSPointArray.alloc( 3 )
        var tDidClosePath: Bool = true
        
        for tIndex in 0 ..< tElementCount
        {
            switch elementAtIndex( tIndex, associatedPoints: tPointsMArray )
            {
                case NSBezierPathElement.MoveToBezierPathElement:
                    CGPathMoveToPoint( tPath, nil, tPointsMArray[0].x, tPointsMArray[0].y )
                
                case NSBezierPathElement.LineToBezierPathElement:
                    CGPathAddLineToPoint( tPath, nil, tPointsMArray[0].x, tPointsMArray[0].y )
                    tDidClosePath = false
                
                case NSBezierPathElement.CurveToBezierPathElement:
                    CGPathAddCurveToPoint( tPath, nil, tPointsMArray[0].x, tPointsMArray[0].y,
                                                       tPointsMArray[1].x, tPointsMArray[1].y,
                                                       tPointsMArray[2].x, tPointsMArray[2].y )
                    tDidClosePath = false
                
                case NSBezierPathElement.ClosePathBezierPathElement:
                    CGPathCloseSubpath( tPath )
                    tDidClosePath = true
            }
            
            if !tDidClosePath
            {
                //  Ensure path is closed
                CGPathCloseSubpath( tPath )
            }
        }
        
        return CGPathCreateCopy( tPath )
    }
}


I'd really appreciate any help/suggestions you guys could offer. It's possible that my bug isn't in this code (although I'm not sure why the values would be different when I print them out), but knowing that would help me focus elsewhere.

Is This A Good Question/Topic? 0
  • +

Replies To: NSBezierPath to CGPathRef in Swift

#2 gouldsc   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 12
  • Joined: 03-December 14

Re: NSBezierPath to CGPathRef in Swift

Posted 25 February 2015 - 12:46 PM

I've found working code in this post.

extension NSBezierPath {
	
	var CGPath: CGPathRef {
	
		get {
			return self.transformToCGPath()
		}
	}
	
	/// Transforms the NSBezierPath into a CGPathRef
	///
	/// :returns: The transformed NSBezierPath
	private func transformToCGPath() -> CGPathRef {
		
		// Create path
		var path = CGPathCreateMutable()
		var points = UnsafeMutablePointer<NSPoint>.alloc(3)
		let numElements = self.elementCount
		
		if numElements > 0 {
			
			var didClosePath = true
			
			for index in 0..<numElements {
				
				let pathType = self.elementAtIndex(index, associatedPoints: points)
				
				switch pathType {
					
				case .MoveToBezierPathElement:
					CGPathMoveToPoint(path, nil, points[0].x, points[0].y)
				case .LineToBezierPathElement:
					CGPathAddLineToPoint(path, nil, points[0].x, points[0].y)
					didClosePath = false
				case .CurveToBezierPathElement:
					CGPathAddCurveToPoint(path, nil, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y)
					didClosePath = false
				case .ClosePathBezierPathElement:
					CGPathCloseSubpath(path)
					didClosePath = true
				}
			}
			
			if !didClosePath { CGPathCloseSubpath(path) }
		}
		
		points.dealloc(3)
		return path
	}
}


My shapes are now drawing as expected. It is interesting though that when I print both the NSBezierPath and the CGPathRef I still get different values.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1