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
}
}
}
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:
Create an optional variable in the GraphingView
to hold a function pointer:
var computeYForX: ((Double) -> Double)?
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.
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
.
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.
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)