Question

I am making a complex shape using a few CGRects and Ellipses. I would like to stroke that path once it has been created. When I stroke the path it strokes each shape. Is there a way I can merge each shape into a single path, so the stroke doesn't intersect?

enter image description here

int thirds = self.height / 3;

CGPathRef aPath = CGPathCreateMutable();

CGRect rectangle = CGRectMake((58 - 10) / 2, 46, 10, self.height - 58);
CGPathAddRect(aPath, nil, rectangle);

CGPathAddEllipseInRect(aPath, nil, CGRectMake((58 - 48) / 2, 0, 48, 48));
CGPathAddEllipseInRect(aPath, nil, CGRectMake((58 - 48) / 2, thirds, 48, 48));
CGPathAddEllipseInRect(aPath, nil, CGRectMake((58 - 48) / 2, thirds * 2, 48, 48));
CGPathAddEllipseInRect(aPath, nil, CGRectMake(0, self.height - 58, 58, 58));
CGPathCloseSubpath(aPath);
CGPathRef other = CGPathCreateCopy(aPath);

CAShapeLayer *square = [CAShapeLayer layer];
square.fillColor = [UIColor colorWithRed:36/255.0f green:56/255.0f blue:82/255.0f alpha:1.0f].CGColor;
square.strokeColor = [UIColor colorWithRed:50/255.0f green:70/255.0f blue:96/255.0f alpha:1.0f].CGColor;
square.path = other;
[self.layer addSublayer:square];

UPDATE

I tried with adding paths together and I got the exact same result

CGPathAddPath(aPath, nil, CGPathCreateWithRect(rectangle, nil));

CGPathAddPath(aPath, nil, CGPathCreateWithEllipseInRect(CGRectMake((58 - 48) / 2, 0, 48, 48), nil));
CGPathAddPath(aPath, nil, CGPathCreateWithEllipseInRect(CGRectMake((58 - 48) / 2, thirds, 48, 48), nil));
CGPathAddPath(aPath, nil, CGPathCreateWithEllipseInRect(CGRectMake((58 - 48) / 2, thirds * 2, 48, 48), nil));
CGPathAddPath(aPath, nil, CGPathCreateWithEllipseInRect(CGRectMake(0, self.height - 58, 58, 58), nil));
Was it helpful?

Solution

If you want a path that only describes the outline of your path composition, you'd need to perform boolean operations on the participating shapes.
There are some good tutorials out there.
E.g.: http://losingfight.com/blog/2011/07/07/how-to-implement-boolean-operations-on-bezier-paths-part-1/

If you just want to achieve the visual appearance of an outline, you could draw 2 shape layers:

  • one that fills a path obtained via CGPathCreateCopyByStrokingPath and
  • one that fills your original path atop

Using the shapes from the code you posted:

int thirds = self.height / 3;

CGMutablePathRef aPath = CGPathCreateMutable();

CGRect rectangle = CGRectMake((58 - 10) / 2, 46, 10, self.height - 58);
CGPathAddRect(aPath, nil, rectangle);

CGPathAddEllipseInRect(aPath, nil, CGRectMake((58 - 48) / 2, 0, 48, 48));
CGPathAddEllipseInRect(aPath, nil, CGRectMake((58 - 48) / 2, thirds, 48, 48));
CGPathAddEllipseInRect(aPath, nil, CGRectMake((58 - 48) / 2, thirds * 2, 48, 48));
CGPathAddEllipseInRect(aPath, nil, CGRectMake(0, self.height - 58, 58, 58));
CGPathCloseSubpath(aPath);

CGPathRef other = CGPathCreateCopyByStrokingPath(aPath, NULL, 12.0, kCGLineCapRound, kCGLineJoinRound, 1.0);
CAShapeLayer *square = [CAShapeLayer layer];
square.fillColor = [UIColor colorWithRed:36/255.0f green:56/255.0f blue:82/255.0f alpha:1.0f].CGColor;
square.path = other;
[self.view.layer addSublayer:square];

CAShapeLayer *square2 = [CAShapeLayer layer];
square2.fillColor = [UIColor colorWithRed:50/255.0f green:70/255.0f blue:96/255.0f alpha:1.0f].CGColor;
square2.path = aPath;
[self.view.layer addSublayer:square2];

That will draw the following shape:
enter image description here

OTHER TIPS

Based on the answer of Thomas Zoechling

For swift3

height = self.bounds.size.height
let thirds = self.height / 3

let aPath: CGMutablePath = CGMutablePath()
let rect = CGRect(x: (58 - 10) / 2, y: 46, width: 10, height: self.height - 58)
aPath.addRect(rect)

aPath.addEllipse(in: CGRect(x: (58 - 48) / 2, y: 0, width: 48, height: 48))
aPath.addEllipse(in: CGRect(x: (58 - 48) / 2, y: thirds, width: 48, height: 48))
aPath.addEllipse(in: CGRect(x: (58 - 48) / 2, y: thirds * 2, width: 48, height: 48))
aPath.addEllipse(in: CGRect(x: 0, y: self.height, width: 48, height: 48))
aPath.closeSubpath()

let other: CGPath = aPath.copy(strokingWithWidth: 12, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
let square = CAShapeLayer()
square.fillColor = UIColor(red: 36/255.0, green: 56/255.0, blue: 82/255.0, alpha: 1.0).cgColor
square.path = other
self.layer.addSublayer(square)

let square2 = CAShapeLayer()
square2.fillColor = UIColor(red: 50/255.0, green: 70/255.0, blue: 96/255.0, alpha: 1.0).cgColor
square2.path = aPath
self.layer.addSublayer(square2)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top