Please note, this blog entry is from a previous course. You might want to check out the current one.
Your graphing UIView must be generic and reusable (i.e. it should be a generic x-y graphing class and know nothing about a CalculatorBrain). Use a protocol to get the graphing view’s data because Views should not own their data.
Basically we need a way to calculate an y value for a given x value, and to know where we want to place the graph and the size of the graph, which we define as protocol:
@protocol GraphViewDataSource - (id)calculateYValueFromXValue:(double)xValue; // NSNumber or string with error @property (nonatomic) CGFloat scale; // 1 = 100% @property (nonatomic) CGPoint origin; // point to place in the middle of the screen @end ... @property (nonatomic, weak) IBOutlet id <GraphViewDataSource> dataSource; ....
and make our controller the data source:
@interface GraphViewController () <GraphViewDataSource>
and
- (void)setGraphView:(GraphView *)graphView { if (graphView == _graphView) return; _graphView = graphView; graphView.dataSource = self; }
As origin and scale are already implemented, we have only to provide the calculation by creating a new dictionary with the given x value and provide it to the model:
- (id)calculateYValueFromXValue:(double)xValue { return [CalculatorBrain runProgram:self.program usingVariableValues:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:xValue], @"x", nil]]; }
Finally we use our data source in the drawing function of the view:
CGFloat scale = self.dataSource.scale; CGPoint origin = self.dataSource.origin; [AxesDrawer drawAxesInRect:area originAtPoint:origin scale:scale]; [[UIColor blackColor] setStroke]; NSInteger widthInPixel = area.size.width * self.contentScaleFactor; CGContextSetLineWidth(context, 2); CGContextBeginPath(context); for (NSInteger xPixel = 0; xPixel <= widthInPixel; xPixel++) { id result = [self.dataSource calculateYValueFromXValue: [self xValueFromPixel:xPixel inRect:area originAtPoint:origin scale:scale]]; if (![result isKindOfClass:[NSNumber class]]) { start = YES; continue; } double y = [result doubleValue]; if (start) { CGContextMoveToPoint(context, [self xPointFromPixel:xPixel inRect:area], [self yPointFromValue:y inRect:area originAtPoint:origin scale:scale] ); start = NO; } else CGContextAddLineToPoint(context, [self xPointFromPixel:xPixel inRect:area], [self yPointFromValue:y inRect:area originAtPoint:origin scale:scale] ); CGContextStrokePath(context);
where we just connect the dots with lines and use a couple of helper functions for the adjustment from/to pixel/points/values:
- (CGFloat)xPointFromPixel:(NSInteger)xPixel inRect:(CGRect)bounds { return xPixel / self.contentScaleFactor + bounds.origin.x; } - (double)xValueFromPixel:(NSInteger)xPixel inRect:(CGRect)bounds originAtPoint:(CGPoint)origin scale:(CGFloat)pointsPerUnit { return ([self xPointFromPixel:xPixel inRect:bounds] - origin.x) / pointsPerUnit; } - (CGFloat)yPointFromValue:(double)y inRect:(CGRect)bounds originAtPoint:(CGPoint)origin scale:(CGFloat)pointsPerUnit { return origin.y - y * pointsPerUnit; }