0

I have a custom UIView I'm doing drawing in, and in the drawRect function I'm creating a circle shape (with a hollow center, so picture a donut) as a UIBezierPath.

I then want to use this UIBezierPath/circle shape as the mask for when I draw my UIImage with drawInRect. So in the end I'll have a donut shape with an image background.

I create the circle/UIBezierPath as follows:

let context = UIGraphicsGetCurrentContext()
let progress = CGFloat(1.0)
let diameter = CGFloat(32.0)

let gapExpression: CGFloat = diameter * CGFloat(M_PI)
let dashExpression: CGFloat = progress * diameter * CGFloat(M_PI)

CGContextSaveGState(context)
CGContextTranslateCTM(context, 19, 19)
CGContextRotateCTM(context, -270 * CGFloat(M_PI) / 180)

var circlePath = UIBezierPath()
circlePath.moveToPoint(CGPointMake(-16, 0))
circlePath.addCurveToPoint(CGPointMake(0, -16), controlPoint1: CGPointMake(-16, -8.84), controlPoint2: CGPointMake(-8.84, -16))
circlePath.addCurveToPoint(CGPointMake(16, 0), controlPoint1: CGPointMake(8.84, -16), controlPoint2: CGPointMake(16, -8.84))
circlePath.addCurveToPoint(CGPointMake(0, 16), controlPoint1: CGPointMake(16, 8.84), controlPoint2: CGPointMake(8.84, 16))
circlePath.addCurveToPoint(CGPointMake(-16, 0), controlPoint1: CGPointMake(-8.84, 16), controlPoint2: CGPointMake(-16, 8.84))
circlePath.closePath()
circlePath.lineCapStyle = kCGLineCapRound;

UIColor.grayColor().setStroke()
circlePath.lineWidth = 5
CGContextSaveGState(context)
CGContextSetLineDash(context, 0, [dashExpression, gapExpression], 2)
circlePath.stroke()
CGContextRestoreGState(context)

Which if I just have that in my drawRect it outputs a circle/donut shape.

However I also have a UIImage:

let image = UIImage(named: "test-shape")

That I draw using drawInRect, but I want the image to be masked by the UIBezierPath above.

How would I accomplish this?

jscs
  • 63,694
  • 13
  • 151
  • 195
Doug Smith
  • 29,668
  • 57
  • 204
  • 388

1 Answers1

1

Save the context, add a clip, set the scale, and draw the image.

This should be close:

circlePath.lineCapStyle = kCGLineCapRound;      // existing

// add these lines
CGContextSaveGState(context)
circlePath.addClip()
CGContextScaleCTM(context, 1, -1)
CGContextDrawTiledImage(context, CGRectMake(0, 0, test-shape.size.width, test-shape.size.height), test-shape.CGImage)
CGContextRestoreGState(context)

UIColor.grayColor().setStroke()                 //existing

Edit to add alternative: This draws a donut and fills it with the image. But the line dash is not probably going to have the effect you need. Perhaps this can help in some way.

let context = UIGraphicsGetCurrentContext()

let testshape = UIImage(named: "testshape.png")!

var circlePath = UIBezierPath()
circlePath.moveToPoint(CGPointMake(9.5, 2.5))
circlePath.addCurveToPoint(CGPointMake(6.1, 3.38), controlPoint1: CGPointMake(8.27, 2.5), controlPoint2: CGPointMake(7.11, 2.82))
circlePath.addCurveToPoint(CGPointMake(2.5, 9.5), controlPoint1: CGPointMake(3.95, 4.58), controlPoint2: CGPointMake(2.5, 6.87))
circlePath.addCurveToPoint(CGPointMake(9.5, 16.5), controlPoint1: CGPointMake(2.5, 13.37), controlPoint2: CGPointMake(5.63, 16.5))
circlePath.addCurveToPoint(CGPointMake(16.5, 9.5), controlPoint1: CGPointMake(13.37, 16.5), controlPoint2: CGPointMake(16.5, 13.37))
circlePath.addCurveToPoint(CGPointMake(9.5, 2.5), controlPoint1: CGPointMake(16.5, 5.63), controlPoint2: CGPointMake(13.37, 2.5))
circlePath.closePath()
circlePath.moveToPoint(CGPointMake(19, 9.5))
circlePath.addCurveToPoint(CGPointMake(9.5, 19), controlPoint1: CGPointMake(19, 14.75), controlPoint2: CGPointMake(14.75, 19))
circlePath.addCurveToPoint(CGPointMake(0, 9.5), controlPoint1: CGPointMake(4.25, 19), controlPoint2: CGPointMake(-0, 14.75))
circlePath.addCurveToPoint(CGPointMake(3.88, 1.84), controlPoint1: CGPointMake(0, 6.36), controlPoint2: CGPointMake(1.53, 3.57))
circlePath.addCurveToPoint(CGPointMake(9.5, 0), controlPoint1: CGPointMake(5.45, 0.68), controlPoint2: CGPointMake(7.4, 0))
circlePath.addCurveToPoint(CGPointMake(19, 9.5), controlPoint1: CGPointMake(14.75, 0), controlPoint2: CGPointMake(19, 4.25))
circlePath.closePath()
circlePath.lineCapStyle = kCGLineCapRound;

CGContextSaveGState(context)
circlePath.addClip()
CGContextScaleCTM(context, 1, -1)
CGContextDrawTiledImage(context, CGRectMake(0, 0, testshape.size.width, testshape.size.height), testshape.CGImage)
CGContextRestoreGState(context)

donut

If you just want a half circle, here is a quick example of how to draw that. Just use that as the mask. Rotate as needed.

let color = UIColor(red: 0.742, green: 0.000, blue: 1.000, alpha: 1.000)

var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(56, 45))
bezierPath.addCurveToPoint(CGPointMake(31, 70), controlPoint1: CGPointMake(56, 58.81), controlPoint2: CGPointMake(44.81, 70))
bezierPath.addCurveToPoint(CGPointMake(26, 65), controlPoint1: CGPointMake(28.24, 70), controlPoint2: CGPointMake(26, 67.76))
bezierPath.addCurveToPoint(CGPointMake(31, 60), controlPoint1: CGPointMake(26, 62.24), controlPoint2: CGPointMake(28.24, 60))
bezierPath.addCurveToPoint(CGPointMake(46, 45), controlPoint1: CGPointMake(39.28, 60), controlPoint2: CGPointMake(46, 53.28))
bezierPath.addCurveToPoint(CGPointMake(31, 30), controlPoint1: CGPointMake(46, 36.72), controlPoint2: CGPointMake(39.28, 30))
bezierPath.addCurveToPoint(CGPointMake(26, 25), controlPoint1: CGPointMake(28.24, 30), controlPoint2: CGPointMake(26, 27.76))
bezierPath.addCurveToPoint(CGPointMake(29.45, 20.24), controlPoint1: CGPointMake(26, 22.78), controlPoint2: CGPointMake(27.45, 20.9))
bezierPath.addCurveToPoint(CGPointMake(31, 20), controlPoint1: CGPointMake(29.94, 20.09), controlPoint2: CGPointMake(30.46, 20))
bezierPath.addCurveToPoint(CGPointMake(56, 45), controlPoint1: CGPointMake(44.81, 20), controlPoint2: CGPointMake(56, 31.19))
bezierPath.closePath()
color.setFill()
bezierPath.fill()

half circle with rounded ends

picciano
  • 22,341
  • 9
  • 69
  • 82
  • I did this, and the result ended up with the gray circular border and the image on the inside of the circle (the image is just a purple square). Here's what it looks like: http://i.imgur.com/bMnnf8p.png, and I'm looking for this: http://i.imgur.com/SEDdfNT.png where the purple is masked by the donut shape. – Doug Smith Aug 07 '15 at 01:37
  • Oh, I see what you're trying to do. Your trying to use the LINE as the mask. I don't think you can do that. You probably need to draw a circle within a circle and use the image as the fill. Let me update answer... – picciano Aug 07 '15 at 01:50
  • Well, sorry. I played around with it, and could get the purple to properly fill the shape, but what you're doing with the dashes is obviously not going to work now. I'll add it to the answer above, maybe it will be useful. – picciano Aug 07 '15 at 02:00
  • That is a big help, thank you! I'm curious though, if I just want donut shaped circle (this is for a progress indicator) to use as a mask, am I overcomplicating it? Is there an easier way to be doing this? I don't need to be doing all the things with dashes, if I can make the stroke not go all the way around (so it's a half circle) that works fine! I just can't help but think I'm overcomplicating this. :/ (New to Core Graphics.) – Doug Smith Aug 07 '15 at 02:11
  • Perhaps draw your half-circle shape, then rotate it as needed? – picciano Aug 07 '15 at 02:15
  • How does one draw a half-circle shape exactly? That's what I was trying to replicate with the dashes. – Doug Smith Aug 07 '15 at 02:15
  • Is it possible to do that while still being able to set the line cap style to round? (kCGLineCapRound) – Doug Smith Aug 07 '15 at 02:22
  • Well, it's a shape, not a line. But yeah, you can certainly draw it with rounded ends that "look" like cap style round. And, I'm just filling it with a solid color, but you can still use an image if you need to. – picciano Aug 07 '15 at 02:32
  • You might even look at this answer for something similar: http://stackoverflow.com/a/31619116/600753 – picciano Aug 07 '15 at 02:35