17

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));
brenjt
  • 15,997
  • 13
  • 77
  • 118

2 Answers2

26

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

Thomas Zoechling
  • 34,177
  • 3
  • 81
  • 112
  • 1
    you sir are a genius and a scholar. Thanks for the explanation. – brenjt Jul 09 '13 at 16:28
  • This is not real union but an imitation of it, you basically draw original shape over the dilated one. It will work in this case, but wouldn't work with transparent colors, different stroke styles, shaders etc. – kelin Jul 31 '22 at 18:27
5

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)
Frankie
  • 51
  • 1
  • 3