cs193p – Project #3 Assignment #3 Extra Task #3

By BrokenSphere (Own work) [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0) or GFDL (http://www.gnu.org/copyleft/fdl.html)], via Wikimedia Commons

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

Preserve origin and scale between launchings of the application. Where should this be done to best respect MVC, do you think?

The easy way would be in the graph view, but that would not respect MVC … Let’s put it in the graph view controller. The only problem is how to know when the origin and scale of the graph view have changed. While can set them easily because they are public variables, we currently have observation mechanism outside the graph-view class. We could add delegation methods to its data-source protocol or better create a delegation protocol …

However we have another way. The gesture recognizers are set in the view controller. Here we can change the target from the graph view to the controller itself:

@IBOutlet weak var graphView: GraphView! {
        didSet {
            graphView.dataSource = self
            graphView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: "zoom:"))
            graphView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "move:"))
            var tap = UITapGestureRecognizer(target: self, action: "center:")
            ...
        }
    }

Now we need three new methods for the gestures, which call the original methods from the graph view, and – when the gestures have ended – set our local variables:

    func zoom(gesture: UIPinchGestureRecognizer) {
        graphView.zoom(gesture)
        if gesture.state == .Ended {
            scale = graphView.scale
            origin = graphView.origin
        }
    }
    
    func move(gesture: UIPanGestureRecognizer) {
        graphView.move(gesture)
        if gesture.state == .Ended {
            origin = graphView.origin
        }
    }
    
    func center(gesture: UITapGestureRecognizer) {
        graphView.center(gesture)
        if gesture.state == .Ended {
            origin = graphView.origin
        }
    }

These local variables are computed ones, getting and setting their values via the user defaults:

    private struct Keys {
        static let Scale = "GraphViewController.Scale"
        static let Origin = "GraphViewController.Origin"
    }
    private let defaults = NSUserDefaults.standardUserDefaults()
    var scale: CGFloat {
        get { return defaults.objectForKey(Keys.Scale) as? CGFloat ?? 50.0 }
        set { defaults.setObject(newValue, forKey: Keys.Scale) }
    }

The origin is a point and as such not directly suitable for the user defaults. We need to convert it to and from an array of numbers:

    private var origin: CGPoint {
        get {
            var origin = CGPoint()
            if let originArray = defaults.objectForKey(Keys.Origin) as? [CGFloat] {
                origin.x = originArray.first!
                origin.y = originArray.last!
            }
            return origin
        }
        set {
            defaults.setObject([newValue.x, newValue.y], forKey: Keys.Origin)
        }
    }

The variables are handed over to the graph view when the graph view gets set up. To let the graph view auto center the view we only provide the origin if there are valid values in the user defaults:

    @IBOutlet weak var graphView: GraphView! {
        didSet {
            ...
            if !resetOrigin {
                graphView.origin = origin
            }
            graphView.scale = scale
        }
    }
    private var resetOrigin: Bool {
        get {
            if let originArray = defaults.objectForKey(Keys.Origin) as? [CGFloat] {
                return false
            }
            return true
        }
    }

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

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

3 thoughts on “cs193p – Project #3 Assignment #3 Extra Task #3”

  1. this is my solution:

    1. define scale and origin as computed properties in the controller; these set and retrieve data from history (same like yours but allow being optionals)
    2. set the data in NSUserDefaults on viewWillDisappear
    3. init graphView with data from NSUserDefaults on viewDidLoad

    thanks for your code!

  2. Great tips! I found a helper function while browsing the web that is a bit easier to use than what you did for storing the origin in NSUserDefaults.

    They’re called NSStringFromCGPoint(CGPoint point) and CGPointFromString(String string).

    The first takes a CGPoint and gives you back a String in the form “{x, y}”, which is helpful for storing the value in NSUserDefaults.

    The second takes a string and gives back a CGPoint. For example, to retrieve the value, I did this:


    if let CGPointFromUserDefaults = NSUserDefaults.standardUserDefaults()
    .objectForKey(Keys.Scale) as? String {
    return CGPointFromString(CGPointFromUserDefaults)
    } else {
    return view.center
    }

    To set the value, just pass in newValue into NSStringFromCGPoint:

    NSStringFromCGPoint(newValue)

    To convert the CGPoint to a string you can set.

    Thanks for the tips, I’ve been following along and they’ve been extremely helpful. Just wanted to pitch in my 2 cents.

  3. Using Delegate :


    protocol GraphViewDelegate {
    func scale(scale: CGFloat, sender: GraphView)
    func origin(origin: CGPoint, sender: GraphView)
    }

    @IBInspectable var pointsPerUnits: CGFloat = 100 {
    didSet {
    setNeedsDisplay()
    delegate?.scale(pointsPerUnits, sender: self)
    }
    }

    var newAxeOrigin: CGPoint?

    private var axeOrigin : CGPoint {
    get {
    return newAxeOrigin ?? convertPoint(center, fromView: superview) }
    set {
    newAxeOrigin = newValue
    delegate?.origin(newValue, sender: self)
    setNeedsDisplay()
    }
    }

    In GraphViewController :

    func scale(scale: CGFloat, sender: GraphView) {
    userDefaults.setObject(scale, forKey: Keys.Scale)
    }

    func origin(origin: CGPoint, sender: GraphView) {
    userDefaults.setObject(NSStringFromCGPoint(origin), forKey: Keys.Origin)
    }

    Finally Set :


    if let scale = userDefaults.objectForKey(Keys.Scale) as? CGFloat {
    graphView.pointsPerUnits = scale
    }

    if let origin = userDefaults.objectForKey(Keys.Origin) as? String {
    graphView.newAxeOrigin = CGPointFromString(origin)
    }

Leave a Reply

Your email address will not be published.