2

I'm writing an app that plots some data into a simple graph and sometimes want to draw out the scale in the background. To do this I have a UIView subclass which acts as the graph background and simply use the drawRect: method to draw the scale (data elements will get added as subviews to this view, since I want the user to be able to interact with them).

However, I also want a gradient background color and have used CAGradientLayer for this purpose (as suggested in this thread). But when I add this as a sublayer, the gradient background appears, but nothing I do in drawRect: shows!

I'm sure I'm missing something simple or have misunderstood how to use CAGradientLayer or something, so any help or suggestions is appreciated!

This is the relevant code in my UIView subclass:

- (id)initWithFrame:(CGRect)frame {

    if (self = [super initWithFrame:frame]) {

         // Create a gradient background
        CAGradientLayer *gradientBackground = [CAGradientLayer layer];
        gradientBackground.frame = self.bounds;
        gradientBackground.colors = [NSArray arrayWithObjects:(id)[[UIColor grayColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
        [self.layer insertSublayer:gradientBackground atIndex:0];
    }

    return self;
}

- (void)drawRect:(CGRect)rect {

    // Get the graphics context
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Clear the context
    CGContextClearRect(context, rect);

    // Call actual draw method 
    [self drawInContext:context];
}

-(void)drawInContext:(CGContextRef)context {

CGFloat step;

// Draw Y scale
if (displayYScale) {

    CGContextSetRGBStrokeColor(context, 0, 0, 0, kScaleLineAlpha);

    if (yAxisScale.mode == FCGraphScaleModeData) {

        step = (self.frame.size.height-(yAxisScale.padding*2))/yAxisScale.dataRange.maximum;
        for (int i = 0; i <= yAxisScale.dataRange.maximum; i++) {

            CGContextMoveToPoint(context, 0.0f, (step*i)+yAxisScale.padding);
            CGContextAddLineToPoint(context, self.frame.size.width, (step*i)+yAxisScale.padding);
        }

    } else if (yAxisScale.mode == FCGraphScaleModeDates) {

        int units = (int)[yAxisScale units];
        step = (self.frame.size.height-(yAxisScale.padding*2))/units;
        for (int i = 0; i <= units; i++) {

            CGContextMoveToPoint(context, 0.0f, (step*i)+yAxisScale.padding);
            CGContextAddLineToPoint(context, self.frame.size.width, (step*i)+yAxisScale.padding);
        }
    }

    CGContextStrokePath(context);
}

// Draw X scale
if (displayXScale) {

    CGContextSetRGBStrokeColor(context, 0, 0, 255, kScaleLineAlpha);

    if (xAxisScale.mode == FCGraphScaleModeData) {

        step = (self.frame.size.width-(xAxisScale.padding*2))/xAxisScale.dataRange.maximum;
        for (int i = 0; i <= xAxisScale.dataRange.maximum; i++) {

            CGContextMoveToPoint(context, (step*i)+xAxisScale.padding, 0.0f);
            CGContextAddLineToPoint(context, (step*i)+xAxisScale.padding, self.frame.size.height);
        }

    } else if (xAxisScale.mode == FCGraphScaleModeDates) {

        int units = (int)[xAxisScale units];
        step = (self.frame.size.width-(xAxisScale.padding*2))/units;
        for (int i = 0; i <= units; i++) {

            CGContextMoveToPoint(context, (step*i)+xAxisScale.padding, 0.0f);
            CGContextAddLineToPoint(context, (step*i)+xAxisScale.padding, self.frame.size.height);
        }
    }

    CGContextStrokePath(context);
}
}

Thanks!

/Anders

Community
  • 1
  • 1
Ansig
  • 311
  • 1
  • 4
  • 7

2 Answers2

5

Since you're drawing anyway, you can also just draw your own gradient:

// the colors
CGColorRef topColor = [[UIColor grayColor] CGColor];
CGColorRef bottomColor = [[UIColor whiteColor] CGColor];
NSArray *colors =
    [NSArray arrayWithObjects: (id)topColor, (id)bottomColor, nil];
CGFloat locations[] = {0, 1};

CGGradientRef gradient =
    CGGradientCreateWithColors(CGColorGetColorSpace(topColor),
                               (CFArrayRef)colors, locations);

// the start/end points
CGRect bounds = self.bounds;
CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y);
CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds));

// draw
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawLinearGradient(context, gradient, top, bottom, 0);

CGGradientRelease(gradient);
nschum
  • 15,322
  • 5
  • 58
  • 56
  • Absolutely, that would do the job too, thanks! I prefer the simplicity of using a CAGradientLayer, though, so am trying that solution first. – Ansig Jul 27 '10 at 09:28
2

Sublayers will show above anything done in drawRect. Add another sublayer on top of the gradient layer that uses your view as the delegate, in that layers drawLayer:inContext: callback do what you're currently doing in drawRect.

Joshua Weinberg
  • 28,598
  • 2
  • 97
  • 90
  • Ah, of course, simple as that! :-) I prefer the simplicity of using a CAGradientLayer and just adding another layer for drawing the scale compared to drawing the gradient myself (although that does the job too). However, when I add another layer (CALayer) with my view as a delegate, the app crashes with a trace (see next comment) ending with: #43651 0x002cb372 in -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] This occurs when I add my view (which does the drawing) as a subview to a scroll view (happens in createAndDisplayGraph:). Any ideas? – Ansig Jul 27 '10 at 09:25
  • More of the trace mentioned in my comment above: #43651 0x002cb372 in -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] #43652 0x002c2844 in -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:] #43653 0x002c9197 in -[UIView(Internal) _addSubview:positioned:relativeTo:] #43654 0x002c2e37 in -[UIView(Hierarchy) addSubview:] #43655 0x00003c9b in -[GraphScrollView createAndDisplayGraph] at GraphScrollView.m:137 – Ansig Jul 27 '10 at 09:26
  • 2
    Whoops, sorry, I misspoke. The view cannot be the delegate of a layer (other than its root layer). Either subclass CALayer and do its draw internally, or make some controller object its delegate – Joshua Weinberg Jul 27 '10 at 12:47
  • I understand, thanks! Just out of curiosity: I am going to try setting up the layers in the view controller and making it the delegate later (no time right now), but did a quick test with an NSObject subclass as delegate just now and had the problem that drawLayer:inContext: in there never got called. Is there a reason for this? (I googled it, but none of the suggestions with e.g. setNeedsDisplay and needsDisplayOnBoundsChange = YES made any difference.) – Ansig Jul 27 '10 at 15:58