views:

1254

answers:

2

As the background for one of the views in my app, I'd like to draw a fairly simple rectangular border just inside its frame. This would essentially be a rectangular gradient: a black line around the frame, fading to white about 10-20 pixels in. Unfortunately, as far as I can tell, Core Graphics doesn't provide rectangular gradients (either with CGGradient or CGShading). So I'm wondering what the best approach would be.

Two that occur to me:

  1. Draw a series of concentric rectangles, each subsequent one lighter in color, and inset by 1px on each side. I can't think of a simpler approach, but I have to do all of the gradient calculations myself, and it might be a lot of graphics operations.
  2. Use CGGradient in linear mode, once for each side. But for this to work, I think I'd need to set up a trapezoidal clipping area for each side first, so that the gradients would be mitered at the corners.

Seems like there should be a way to use path stroking to do this, but it doesn't seem like there's a way to define a pattern that's oriented differently on each side.

+3  A: 

I would go with option #2:

Use CGGradient in linear mode, once for each side. But for this to work, I think I'd need to set up a trapezoidal clipping area for each side first, so that the gradients would be mitered at the corners.

Using NSBezierPath to create the trapezoidal regions would be fairly straightforward, and you would only have to perform four drawing operations.

Here's the basic code for creating the left side trapezoidal region:

NSRect outer = [self bounds];
NSPoint outerPoint[4];
outerPoint[0] = NSMakePoint(0, 0);
outerPoint[1] = NSMakePoint(0, outer.size.height);
outerPoint[2] = NSMakePoint(outer.size.width, outer.size.height);
outerPoint[3] = NSMakePoint(outer.size.width, 0);

NSRect inner = NSInsetRect([self bounds], borderSize, borderSize);
NSPoint innerPoint[4];
innerPoint[0] = inner.origin;
innerPoint[1] = NSMakePoint(inner.origin.x,
                            inner.origin.y + inner.size.height);
innerPoint[2] = NSMakePoint(inner.origin.x + inner.size.width,
                            inner.origin.y + inner.size.height);
innerPoint[3] = NSMakePoint(inner.origin.x + inner.size.width,
                            inner.origin.y);

NSBezierPath leftSidePath = [[NSBezierPath bezierPath] retain];
[leftSidePath moveToPoint:outerPoint[0]];
[leftSidePath lineToPoint:outerPoint[1]];
[leftSidePath lineToPoint:innerPoint[1]];
[leftSidePath lineToPoint:innerPoint[0]];
[leftSidePath lineToPoint:outerPoint[0]];

// ... etc.

[leftSidePath release];
e.James
Speaking as someone who (obviously?) hasn't done a lot of graphics programming on the iPhone (or Mac) yet... is there a reason to prefer NSBezierPath? Most examples I've seen concentrate on the Core Graphics APIs.
Sixten Otto
Well, I used it here because I've used it before, so know that it works. :) It is entirely possible that Core Graphics can do it better. No harm in giving it a try, but be sure to profile your code to see if you are getting a measurable performance increase (or if you actually need one, for that matter).
e.James
This isn't exactly the code I ended up with, but it's petty similar. Never did come up with a better solution than drawing four trapezoids.
Sixten Otto
A: 

something like this could also work. basically: instead of using clipping paths, simply use blendmode. and in this example the gradient is cached in a CGLayer.

 CGContextRef ctx = UIGraphicsGetCurrentContext();
 CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();

 CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0);
 CGContextFillRect(ctx,self.bounds);

 CGFloat w = self.bounds.size.width;
 CGFloat h = self.bounds.size.height;
 CGFloat dh = (w-h)/2;

 CGLayerRef l = CGLayerCreateWithContext(ctx,CGSizeMake(h,48.0f),NULL);
 CGContextRef lctx = CGLayerGetContext(l);

 float comp[] = { .2,.5,1.0,1.0,1.0,1.0,1.0,1.0};
 CGGradientRef gradient = CGGradientCreateWithColorComponents(cspace, comp, NULL, 2);
 CGContextDrawLinearGradient(lctx, gradient,CGPointMake(0,0),CGPointMake(0,48), 0);

 CGContextSaveGState(ctx);
 CGContextSetBlendMode(ctx,kCGBlendModeDarken);
 for(int n=1;n<5;n++)
 {
  CGContextTranslateCTM(ctx,w/2.0,h/2.0);
  CGContextRotateCTM(ctx, M_PI_2);
  CGContextTranslateCTM(ctx,-w/2.0,-h/2.0);
  CGContextDrawLayerAtPoint(ctx,CGPointMake((n%2)*dh,(n%2)*-dh),l);
 }
 CGContextRestoreGState(ctx);
arri