cs193p – Project #3 Assignment #3 Task #8

von Goofy50 (Eigenes Werk) [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons

Please note, this blog entry is from a previous course. You might want to check out the current one.

The graphing view must not own (i.e. store) the data it is graphing. It must use delegation to obtain the data as it needs it.

Let’s start defining a protocol for a data source providing an y value for a given x value:

protocol GraphViewDataSource: class {
    func y(x: CGFloat) -> CGFloat?
}


Inside the graph-view class this method will be used via a data-source property:

weak var dataSource: GraphViewDataSource?

… and because the class should be generic, we throw in two additional properties to define the color and the line width of the graph:

    var lineWidth: CGFloat = 1.0 { didSet { setNeedsDisplay() } }
    var color: UIColor = UIColor.blackColor() { didSet { setNeedsDisplay() } }

To draw the graph, create a path, set its color and width, and loop over all pixels to draw the graph and finally stroke it. To get the correct values to send to the data source we need to adjust for the offset of the origin and current scale, and the other way around to calculate the correct point for drawing. If there is a valid result move to the point – for the first one – and draw a line to that point for all other ones:

    override func drawRect(rect: CGRect) {
        ...
        color.set()
        let path = UIBezierPath()
        path.lineWidth = lineWidth
        var firstValue = true
        var point = CGPoint()
        for var i = 0; i <= Int(bounds.size.width * contentScaleFactor); i++ {
            point.x = CGFloat(i) / contentScaleFactor
            if let y = dataSource?.y((point.x - origin.x) / scale) {
                if !y.isNormal && !y.isZero {
                    continue
                }
                point.y = origin.y - y * scale
                if firstValue {
                    path.moveToPoint(point)
                    firstValue = false
                } else {
                    path.addLineToPoint(point)
                }
            }
        }
        path.stroke()
    }

Connect the graph view in the storyboard to the graph view controller, and use it’s observer to define it as data source for the graph view:

class GraphViewController: UIViewController, GraphViewDataSource
...
    @IBOutlet weak var graphView: GraphView! {
        didSet {
            graphView.dataSource = self
        }
    }

To implement the method from the protocol, set the current x value as the value of the variable and evaluate the calculator brain:

    func y(x: CGFloat) -> CGFloat? {
        brain.variableValues["M"] = Double(x)
        if let y = brain.evaluate() {
            return CGFloat(y)
        }
        return nil
    }

In task #4 we promised to keep the calculator brain private. Thus we use the program property (from the lecture) to pass the contents of the calculator brain from the calculator view controller to the graph view controller. Whenever the public program property is set, we feed it to our “local” calculator brain:

    private var brain = CalculatorBrain()
    typealias PropertyList = AnyObject
    var program: PropertyList {
        get {
            return brain.program
        }
        set {
            brain.program = newValue
        }
    }

This handover happens during the preparation of the segue:

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
                ...
                case "plot graph":
                    ...
                    gvc.program = brain.program
                ...
    }

… one minor change to the calculator brain remains because during the lecture we did not know – or at least the instructor did not tell us – that we would need variables later on. While the current implementation is able to provide a property list including variables, it does not know how to add them back to the calculator brain:

    var program: PropertyList { // guaranteed to be a property list
        ...
        set {
                        ...
                    } else {
                        newOpStack.append(.Variable(opSymbol))
                    }
                ...
        }
    }

The complete code for the task #8 is available on GitHub.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

Leave a Reply

Your email address will not be published.