Learning Swift: Calculator App, part 5. Revamping the logic/design

This is part 5 of my Learning Swift Calculator App.

This update is mostly about the code structure and design decision about the calculator and its behavior.

Technical lessons learned:

  • Adding variable values in the middle of the string is so much easier using the following format – “blah blah blah and I am going to add /(variable) in the middle of it”  Assuming that variable:String = “some words” then the previous string becomes “blah blah blah and I am going to add some words in the middle of it”
  • Be careful when modifying IBOutlets.  Renaming it in Viewcontroller.swift does not modify the IBOutlet link (in the Storyboard).  You have to relink it.  The error given by xCode is not particular helpful either.

Design lesson learned:

  • Contextual buttons would make the code so much cleaner, but calculators have been around so long that people expect certain behaviors.
    • An example of what I mean by contextual button is this: after pressing = it would make it much clearer to the user what the calculator is actually doing if the equal button disappeared.  However, many of us have come to expect the equal button to mean, repeat last operation.  So, if you press 1+1= (the calculator shows 2), then if you press = again, the calculator will show 3.

After spending a few hours banging my head against some weird behavior, I started from scratch with a state diagram.

The general gist is as follows:

State diagram for calculator
State diagram for calculator

This is generally being modeled after the OS X calculator.  The tricky part is that the operators (+,-,x, etc.) do different things depending on what the user input was previous to pressing the operator buttons.  If a user presses + after a calculation already took place, the calculator is essentially doing a = and then + presenting the user with the calculated value, then getting ready to accept the next input value.  This is completely different from my original design, where I took a string and processed the calculation when the user pressed =.

I’ve already spent a few hours on this logic and it’s much more complicated than I thought.

Here’s the code so far.  Still very buggy.

 

//
//  ViewController.swift
//  Calculator
//
//  Created by Jin S. An on 10/1/14.
//  Copyright (c) 2014 jinsungpsu.com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet var calcDisplay1: UITextField!
    @IBOutlet var calcDisplayOp: UITextField!
    
    var clearDisplay:Bool = false
    var firstOp:Bool = true
    var num1:Float = 0.0
    var num2:Float = 0.0
    
    
    @IBOutlet var btn01: UIButton!
    @IBOutlet var btn02: UIButton!
    @IBOutlet var btn03: UIButton!
    @IBOutlet var btn04: UIButton!
    @IBOutlet var btn05: UIButton!
    @IBOutlet var btn06: UIButton!
    @IBOutlet var btn07: UIButton!
    @IBOutlet var btn08: UIButton!
    @IBOutlet var btn09: UIButton!
    @IBOutlet var btnDecimal: UIButton!
    @IBOutlet var btn00: UIButton!
    @IBOutlet var btnEqual: UIButton!
    @IBOutlet var btnClear: UIButton!
    @IBOutlet var btnPlus: UIButton!
    @IBOutlet var btnMinus: UIButton!
    @IBOutlet var btnMultiply: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        btn01.showsTouchWhenHighlighted = true;
        btn02.showsTouchWhenHighlighted = true;
        btn03.showsTouchWhenHighlighted = true;
        btn04.showsTouchWhenHighlighted = true;
        btn05.showsTouchWhenHighlighted = true;
        btn06.showsTouchWhenHighlighted = true;
        btn07.showsTouchWhenHighlighted = true;
        btn08.showsTouchWhenHighlighted = true;
        btn09.showsTouchWhenHighlighted = true;
        btn00.showsTouchWhenHighlighted = true;
        btnDecimal.showsTouchWhenHighlighted = true;
        btnEqual.showsTouchWhenHighlighted = true;
        btnClear.showsTouchWhenHighlighted = true;
        btnPlus.showsTouchWhenHighlighted = true;
        btnMinus.showsTouchWhenHighlighted = true;
        btnMultiply.showsTouchWhenHighlighted = true;
        self.view.backgroundColor = UIColor.lightGrayColor()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func digitPressed(buttonPressed: String) {
        if (calcDisplayOp.text != "") {
            firstOp = false
        }
        if (clearDisplay == true){
            calcDisplay1.text = ""
            clearDisplay = false
        }
        calcDisplay1.text = (calcDisplay1.text as NSString)+buttonPressed
    }
    
    func calculateValue(op: String, n1: Float, n2: Float) -> Float{
        var calculatedValue:Float = 0.0
        switch (op) {
            case "+":
                calculatedValue = n1 + n2
            case "-":
                calculatedValue = n1 - n2
            case "*":
                calculatedValue = n1 * n2
            default:
                calculatedValue = -9999.9999
        }
        return calculatedValue
    }

    
    func operatorPressed(buttonPressed: String) {
        calcDisplayOp.text = buttonPressed
        
        if (calcDisplay1.text == "") { // if user press op before any digits, then assume that num1 = 0
            
        } else { // this means that there is a number on display that the next operation needs to do something against.
            if (firstOp == true) { // this is the first operation... don't present a calculated value until 2nd input from user
                num1 = (calcDisplay1.text as NSString).floatValue
            } else { //
                num2 = (calcDisplay1.text as NSString).floatValue
                calcDisplay1.text = "\(calculateValue(buttonPressed, n1: num1, n2: num2))"
                num1 = num2
            }
        }

        clearDisplay = true
        
        /* CASES

        EXPECTED CASE:
        
        THERE IS A NUMBER ON THE DISPLAY, OPERATOR 
        
        
        ERROR CASES:
    
        NOTHING ON THE DISPLAY, USER PRESSES AN OPERATION (OS X CALCULATOR ASSUMES FIRST NUMBER IS 0)
        
        
        AMBIGUOUS CASES:
        
        USER PRESSES AN OPERATOR FOLLOWING AN OPERATOR (OS X CALCULATOR OVERRIDES OPERATOR WITH LATEST PRESSED)
        
        
        
        */
    }
    
    @IBAction func calcEqual(sender: AnyObject) {
        var currOp:String = calcDisplayOp.text
        calcDisplayOp.text = "="
        
        num2 = (calcDisplay1.text as NSString).floatValue
        calcDisplay1.text = "\(calculateValue(currOp, n1: num1, n2: num2))"
        num1 = num2
        
        firstOp = true
        
        clearDisplay = true
    }

    @IBAction func calc01(sender: AnyObject) {
        digitPressed("1")
    }
    @IBAction func calc02(sender: AnyObject) {
        digitPressed("2")
    }
    @IBAction func calc03(sender: AnyObject) {
        digitPressed("3")
    }
    @IBAction func calc04(sender: AnyObject) {
        digitPressed("4")
    }
    @IBAction func calc05(sender: AnyObject) {
        digitPressed("5")
    }
    @IBAction func calc06(sender: AnyObject) {
        digitPressed("6")
    }
    @IBAction func calc07(sender: AnyObject) {
        digitPressed("7")
    }
    @IBAction func calc08(sender: AnyObject) {
        digitPressed("8")
    }
    @IBAction func calc09(sender: AnyObject) {
        digitPressed("9")
    }
    @IBAction func calcDecimal(sender: AnyObject) {
        digitPressed(".")
    }
    @IBAction func calc00(sender: AnyObject) {
        digitPressed("0")
    }
    @IBAction func calcPlus(sender: AnyObject) {
        operatorPressed("+")
    }
    @IBAction func calcMinus(sender: AnyObject) {
        operatorPressed("-")
    }
    @IBAction func calcMultiply(sender: AnyObject) {
        operatorPressed("x")
   }
    @IBAction func calcClear(sender: AnyObject) {
        calcDisplayOp.text = ""
        calcDisplay1.text = ""
        calcDisplayOp.text = ""
        num1 = 0;
        num2 = 0;
        firstOp = true
        clearDisplay = false
    }
}


Posted

in

, , ,

by

Tags: