Metin S.
Metin S.

Reputation: 68

Having trouble plotting the x vs. y graph on Stanford CS193P Assignment 3 (2016)

I am a beginner in iOS development. I have done everything on Assignment 3 (Graphing Calculator) of the 2016 Stanford CS193P iOS development course on iTunes U (gestures, view controllers, views, segues etc.) apart from actually plotting the x vs. y graph. I am quite confused about where and how to put my code to do this; and where the drawRect is going to get the y value from. I have searched the Internet for a solution for ages. I'm assuming the model to the graph view controller is the CalculatorBrain or maybe the program of the brain but I'm not quite sure as to how the view is going to talk to this controller from the drawRect function to get the y value for a point x. If you could put me on the right track that would be very helpful. I'll paste my graph view controller and graph view code below. Thanks in advance.

//GraphViewController

import UIKit

class GraphViewController: UIViewController {

    private var brain = CalculatorBrain()

    var program: [AnyObject]?

    @IBOutlet weak var graphView: GraphingView! {
        didSet {
            graphView.addGestureRecognizer(UIPinchGestureRecognizer(target: graphView, action: #selector (GraphingView.changeScale(_:))))
            graphView.addGestureRecognizer(UIPanGestureRecognizer(target: graphView, action: #selector(GraphingView.panView(_:))))
            graphView.addGestureRecognizer(UITapGestureRecognizer(target: graphView, action: #selector(GraphingView.doubleTapOrigin(_:))))
        }
    }
}




//GraphingView
import UIKit

@IBDesignable
class GraphingView: UIView {

    @IBInspectable var scale: CGFloat = 50 { didSet { setNeedsDisplay() } }
    @IBInspectable var linewidth: CGFloat = 2 { didSet { setNeedsDisplay() } }
    var origin: CGPoint! { didSet { setNeedsDisplay() } }

    /* Gesture Code

    func changeScale (recognizer: UIPinchGestureRecognizer) {
        switch recognizer.state {
        case .Changed,.Ended:
            scale *= recognizer.scale
            recognizer.scale = 1.0
        default:
            break
        }
    }

    func panView (recognizer: UIPanGestureRecognizer) {
        switch recognizer.state {
        case .Changed,.Ended:
            origin.x += recognizer.translationInView(self).x
            origin.y += recognizer.translationInView(self).y
            recognizer.setTranslation(CGPoint(x:0,y:0), inView: self)
        default:
            break
        }
    }

    func doubleTapOrigin (recognizer: UITapGestureRecognizer) {
        recognizer.numberOfTapsRequired = 2
        switch recognizer.state {
        case .Ended :
            origin = CGPoint(x: recognizer.locationInView(self).x, y:recognizer.locationInView(self).y)
        default: break
        }
    }
    */

    var axesDrawer = AxesDrawer()

    override func drawRect(rect: CGRect) {

        if origin == nil {
            origin = CGPoint(x: bounds.midX, y: bounds.midY)
        }
        axesDrawer.drawAxesInRect(rect, origin: origin, pointsPerUnit: scale)

        var pixelX = 0

        while pixelX <= bounds.maxX {

            //do some code to get value of y for current x from the brain in the view controller and plot it with UIBezierPath

            pixelX += 1/contentScaleFactor
        }

    }

}

Upvotes: 1

Views: 518

Answers (2)

abby yorker
abby yorker

Reputation: 417

I used existing functionality in calculator brain. Specifically, inside brain, I created a public method that made a calculation based on a program, stored as any object through the property list typecast (we did that in the save/restore part). All the commands were there - I just packaged it in a method. I use this method for a lot of stuff - so I never rewrite code to evaluate a sequence of operands.

In the public interface, that is just a function that is accessible from any instance of brain. In particular, in preparing for segue to the graph view, I pointed the graph view controller to this function. I also passed the program existing in the brain at that moment, so that any expression valid in the calculator can be plotted. The latter was a goal for me because it makes sense. I wanted to avoid rewriting the function that interprets the operands.

The graph view requires a function (can be brain or any other specification) and a "program" that is required in order to plot expressions. So it is a general class requiring a specific form for describing the operations. I take that as a perfectly "legal" solution.

Drawrect then used the function and the program to draw the curves.

Upvotes: -1

vacawama
vacawama

Reputation: 154691

Updated Answer - Using a Function Pointer

The previous answer is one way to do it using a data source. It is modeled after the way a UITableViewController gets its data.

Reading though the assignment, it seems the Prof wants you to use an optional function pointer instead.

Do the following:

  1. Create an optional variable in the GraphingView to hold a function pointer:

    var computeYForX: ((Double) -> Double)?
    
  2. Apply didSet to that variable and have it call self.setNeedsDisplay() when it is set. This will tell iOS that drawRect needs to be called.

  3. In your calculator when it is time to draw, set the computeYForX property of the GraphingView to a (Double) -> Double function in your GraphViewController. If you want to remove the function (for instance, when someone resets the calculator), set the property to nil.

  4. In drawRect, check to make sure computeYForX is not nil before using it to graph the function. Use guard or if let to safely unwrap the function before using it.

Previous Answer - Using a DataSource

You need to add a Data Source to your GraphingView. First define a protocol called GraphDataSource:

protocol GraphDataSource: class {
    func computeYforX(x: Double) -> Double
}

Add a dataSource property to the GraphingView:

class GraphingView: UIView {
    weak var dataSource: GraphDataSource?

Have your GraphViewController implement that protocol by adding GraphDataSource to the class definition line, by implementing computeYForX(), and by setting itself as the dataSource in didSet for graphView:

class GraphViewController: UIViewController, GraphDataSource {

    private var brain = CalculatorBrain()

    var program: [AnyObject]?

    @IBOutlet weak var graphView: GraphingView! {
        didSet {
            graphView.dataSource = self
            graphView.addGestureRecognizer(UIPinchGestureRecognizer(target: graphView, action: #selector (GraphingView.changeScale(_:))))
            graphView.addGestureRecognizer(UIPanGestureRecognizer(target: graphView, action: #selector(GraphingView.panView(_:))))
            graphView.addGestureRecognizer(UITapGestureRecognizer(target: graphView, action: #selector(GraphingView.doubleTapOrigin(_:))))
        }
    }

    func computeYForX(x: Double) -> Double {
        // call the brain to calculate y and return it
    }
}

Then in drawRect, call computeYForX() on the dataSource when you need a y value:

let y = dataSource?.computeYForX(x)

Upvotes: 1

Related Questions