1

I have a UIView that programmatically draws a "sunburst" pattern using UIBezierPath. Now I would like to extend this by masking the edges with a gradient -- effectively making each "burst" fade from opaque to transparent. I'm think this could be done with a CAGradientLayer mask but I'm not sure how to make it circular.

This is what I'm trying -- it masks the view but the gradient is linear:

    CAGradientLayer *l = [CAGradientLayer layer];
    l.frame = CGRectMake(0.0f, 0.0f, rect.size.width, rect.size.height);
    l.cornerRadius = rect.size.width/2.0f;
    l.masksToBounds = YES;
    l.colors = [NSArray arrayWithObjects:(id)[UIColor blackColor].CGColor, (id)[UIColor clearColor].CGColor, nil];
    self.layer.mask = l;

I'm open to not using CAGradientLayer if anyone knows any other ways to mask a view with a circle with blurred edges.

SOLUTION Thanks to matt's insight, I ended up drawing a mask view using CGContextDrawRadialGradient, rendering that as a UIImage, and using that as a mask layer. If anyone is interested in this process, it is being used in this test project.

Community
  • 1
  • 1
Keller
  • 17,051
  • 8
  • 55
  • 72

3 Answers3

1

An obvious approach is to draw this effect manually. Start very small and draw the sunburst larger and larger and (at the same time) less and less opaque.

On the other hand, it might be sufficient to make a radial gradient and use it as a mask (vignette effect). Core Graphics will draw the radial gradient for you:

https://developer.apple.com/library/ios/#documentation/graphicsimaging/reference/CGContext/Reference/reference.html#//apple_ref/c/func/CGContextDrawRadialGradient

I'm also very fond of CIFilters for adding touches like this: you should look through the catalogue and see what strikes your fancy:

https://developer.apple.com/library/ios/#documentation/graphicsimaging/reference/CoreImageFilterReference/Reference/reference.html

CIFilter actually gives you a sunburst transition that might suit your purposes, especially if combined with masking and compositing; here's a discussion of the sunburst (used in an animation), from my book:

http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I have a feeling that CGContextDrawRadialGradient might be the way to go for this. I'll give this a try. – Keller Apr 16 '13 at 20:50
  • I'm thinking you should start with CGContextDrawRadialGradient and *then* blur the drawing of the gradient with CIFilter and use the result of *that* as a mask. :) – matt Apr 16 '13 at 21:11
  • I'm REALLY close with this, which is why I accepted the answer. I have been using a CGContextDrawRadialGradient to simply draw a circular gradient over my original 'sunburst' view. In effect, this works fine if I match the gradient color to the background color. But how would I actually use my gradient UIView to mask the original sunburst UIView (the use case being if I use this over a non-solid background)? – Keller Apr 17 '13 at 16:08
  • The only way I've figured out how was to render the mask view to a UIImage, then use that as the contents of the layer mask. In other words, is there any way to go from UIView to mask directly? – Keller Apr 17 '13 at 16:20
  • 1
    Sorry, what do you think is wrong with what you're doing? A mask is a layer; a layer has contents which are a CGImage; clearly you need to obtain a CGImage *somehow*; it sounds like you *are* obtaining it; what's the problem? – matt Apr 17 '13 at 16:55
  • Look at the last code example in this section of my book: http://www.apeth.com/iOSBook/ch16.html#_shadows_borders_and_more where I generate a generalized mask for rounding corners of any view. It sounds like what you're doing is exactly what I'm doing! – matt Apr 17 '13 at 16:58
  • Ahh, I see. The disconnect was that I didn't realize a layer's contents are necessarily a CGImage. I was hoping to go straight from UIView->CALayer, but I guess that is not possible and the UIImage/CGImage rendering is a required step. In any case, it works and is plenty fast. Thanks a ton for your help! – Keller Apr 17 '13 at 17:10
  • Well, you *can* draw a layer's content in real time by subclassing or through a delegate, but I hardly see why that's any improvement; it just makes the architecture more complicated, and for no reason - unless you need to draw this mask repeatedly. Again, you might want to read the appropriate section of my book: http://www.apeth.com/iOSBook/ch16.html#_drawing_in_a_layer – matt Apr 17 '13 at 17:16
0

CAGradientLayer draws linear gradients.

You could try to set a shadow and see if that suits your needs:

l.shadowColor = [UIColor blackColor];
l.shadowRadius = rect.size.width / 2;
Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
  • It does not, but thank you. I'm obviously open to not using CAGradientLayer if there is another way to make a circular blurry-edge mask. – Keller Apr 16 '13 at 18:03
0

You can do this by creating a CALayer instance and give it a delegate that implements drawLayer:inContext: and draws a radial gradient in the implementation of that method. Then use that CALayer as your mask.