Search code examples
ioscalayer

Path-based mask with nice anti-aliasing


I'd like to mask a square with a circle. I'm using this instead of corner radius because of something I'd like to do with animation later.

I can get it to mask, but the edges are very rough:

enter image description here

  // Target View
  let targetView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
  targetView.backgroundColor = UIColor.redColor()

  // Mask
  let maskingPath = UIBezierPath()
  let half = targetView.frame.width / 2
  maskingPath.addArcWithCenter(CGPoint(x: half, y: half), radius: half, startAngle: 0, endAngle: 360, clockwise: true)
  let maskingLayer = CAShapeLayer()
  maskingLayer.path = maskingPath.CGPath

  // Maybe setting contentsScale?
  // maskingLayer.contentsScale = UIScreen.mainScreen().scale // doesn't change anything, sorry 
  // Maybe enabling edgeAntialiasing?
  // maskingLayer.allowsEdgeAntialiasing = true // also doesn't change anything
  // Magnification filter?
  // maskingLayer.magnificationFilter = kCAFilterNearest // nuttin'

  targetView.layer.mask = maskingLayer

I've tried magnificationFilter and a few other things.

How do I add an animatable circle mask that's antialiased?


Solution

  • You are giving addArcWithCenter an angle in degrees:

    maskingPath.addArcWithCenter(CGPoint(x: half, y: half),
                                 radius: half,
                                 startAngle: 0,
                                 endAngle: 360,
                                 clockwise: true)
    

    but the documentation states that the angle should be in radians.

    Because of this, you're creating a path that overlaps itself, several times. The edge is drawn on top of previous passes of the edge, in the same place. After enough passes build up, they end up looking opaque (or nearly so). You would see something similar if you created multiple circle paths on top of each other.

    This works better for me:

    maskingPath.addArcWithCenter(CGPoint(x: half, y: half),
                                 radius: half,
                                 startAngle: 0,
                                 endAngle: CGFloat(M_PI * 2.0),
                                 clockwise: true)
    

    (But since you really want a closed oval, you should use UIBezierPath(ovalInRect: targetView.bounds), as you did in your answer.)