10
\$\begingroup\$

I recently started learning Swift through Stanford iOS8 Course. The first assignment is to add some features to simple Calculator which was presented during lectures.

List of tasks:

  • add button for entering decimal point (appendDot())

  • add sin, cos and Pi operations

  • add Label which shows history of performed operations (history and addToHistory(recordToAddToHistory: String))

  • add clear button which clears both 'screens' and sets Calculator to the same state as at app startup (clear())

  • add backspace button (undo()) and button for changing sign of input (changeSign())

I would be very grateful if you would look at my code and give me some feedback about it.

I have doubts about the way I have implemented logic for decimal point button. In hints for the assignment is written that one can use rangeOfString(substring: String) method, but I don't see how it applies to given problem. Also there is a hint, that whole logic can be a one-liner.

ViewController.swift:

import UIKit

class ViewController: UIViewController {


    @IBOutlet weak var display: UILabel!

    @IBOutlet weak var history: UILabel!

    @IBOutlet weak var changeSignButton: UIButton!

    var userIsInTheMiddleOfTypingANumber: Bool = false
    var userInsertedADot: Bool = false
    let brain = CalculatorBrain()

    @IBAction func appendDigit(sender: UIButton) {
        let digit = sender.currentTitle!
        if userIsInTheMiddleOfTypingANumber {
            display.text = display.text! + digit
        }else{
            display.text = digit
            userIsInTheMiddleOfTypingANumber = true
        }
        addToHistory(digit)
    }


    @IBAction func appendDot() {
        if !userInsertedADot && userIsInTheMiddleOfTypingANumber {
            display.text = display.text! + "."
            userInsertedADot = true
            addToHistory(".")
        }
        if !userInsertedADot && !userIsInTheMiddleOfTypingANumber {
            display.text =  "."
            userInsertedADot = true
            userIsInTheMiddleOfTypingANumber = true
            addToHistory(".")
        }
    }

    @IBAction func operate(sender: UIButton) {
        if userIsInTheMiddleOfTypingANumber {
            enter()
        }
        if let operation = sender.currentTitle {
            if let result = brain.performOperation(operation) {
                displayValue = result
            } else {
                displayValue = 0
            }
            addToHistory(operation)
        }


    }

    @IBAction func clear() {

        display.text = "0"
        history.text = " "
        brain.clearOperationStack()

        userIsInTheMiddleOfTypingANumber = false
        userInsertedADot = false

    }


    @IBAction func undo() {

        if count(display.text!) > 1 {
            display.text = dropLast(display.text!)
        }else{
            display.text = "0"
        }
    }

    @IBAction func changeSign() {
        if userIsInTheMiddleOfTypingANumber {
            if Array(display.text!)[0] == "-" {
                display.text = dropFirst(display.text!)
            }else {
                display.text = "-" + display.text!
            }
        }else {
            operate(changeSignButton)
        }

    }


    @IBAction func enter() {
        userIsInTheMiddleOfTypingANumber = false
        userInsertedADot = false
        if let result = brain.pushOperand(displayValue) {
            displayValue = result
        } else {
            displayValue = 0
        }
        addToHistory(" ")
    }

    var displayValue: Double {
        get {
            return NSNumberFormatter().numberFromString(display.text!)!.doubleValue
        }
        set {
            display.text = "\(newValue)"
            userIsInTheMiddleOfTypingANumber = false
        }
    }



    func addToHistory(recordToAddToHistory: String) {
        history.text = history.text! + recordToAddToHistory
    }

}

CalculatorBrain.swift:

import Foundation

class CalculatorBrain {

    private enum Op: Printable {
        case Operand(Double)
        case UnaryOperation(String, Double -> Double)
        case BinaryOperation(String, (Double, Double) -> Double)
        case EmptyOperation(String, Double)

        var description: String {
            get {
                switch self {
                case Operand(let operand):
                    return "\(operand)"
                case .UnaryOperation(let symbol, _):
                    return symbol
                case .BinaryOperation(let symbol, _):
                    return symbol
                case .EmptyOperation(let symbol, _):
                    return symbol
                }
            }
        }

    }

    private var opStack = [Op]()

    private var knownOps = [String:Op]()

    init() {

        func learnOp(op: Op) {
            knownOps[op.description] = op
        }

        learnOp(Op.BinaryOperation("×", *))
        learnOp(Op.BinaryOperation("÷"){$1 / $0})
        learnOp(Op.BinaryOperation("+", +))
        learnOp(Op.BinaryOperation("−") {$1 - $0})
        learnOp(Op.UnaryOperation("√", sqrt))
        learnOp(Op.UnaryOperation("sin", sin))
        learnOp(Op.UnaryOperation("cos", cos))
        learnOp(Op.EmptyOperation("π", M_PI))
        learnOp(Op.UnaryOperation("±"){-$0})

    }

    private func evaluate(ops: [Op]) -> (result: Double?, remainingOps: [Op]) {


        if !ops.isEmpty {
            var remainingOps = ops
            let op = remainingOps.removeLast()

            switch op {
            case .Operand(let operand):
                return (operand, remainingOps)
            case .UnaryOperation(_, let operation):
                let operandEvaluation = evaluate(remainingOps)
                if let operand = operandEvaluation.result {
                    return (operation(operand), operandEvaluation.remainingOps)
                }

            case .BinaryOperation(_, let operation):
                let op1Evaluation = evaluate(remainingOps)
                if let operand1 = op1Evaluation.result {
                    let op2Evaluation = evaluate(op1Evaluation.remainingOps)
                    if let operand2 = op2Evaluation.result {
                        return (operation(operand1, operand2), op2Evaluation.remainingOps)
                    }
                }
            case .EmptyOperation(_, let operation):
                return (operation, remainingOps)
            }
        }

        return (nil, ops)

    }

    func evaluate() -> Double? {
        let (result, remainder) = evaluate(opStack)
        println("\(opStack) = \(result) with \(remainder) left over")
        return result
    }

    func pushOperand(operand: Double) -> Double? {

        opStack.append(Op.Operand(operand))

        return evaluate()
    }

    func performOperation(symbol: String) -> Double? {
        if let operation = knownOps[symbol] {
            opStack.append(operation)
        }
        return evaluate()
    }


    func clearOperationStack(){
        opStack = [Op]()
    }

}
\$\endgroup\$
  • \$\begingroup\$ Link to course? \$\endgroup\$ – Legato May 11 '15 at 5:17
  • 1
    \$\begingroup\$ The course is available on iTunes U itunes.apple.com/us/course/developing-ios-8-apps-swift/… \$\endgroup\$ – Sebastian Osiński May 11 '15 at 5:19
  • \$\begingroup\$ Does this course allow you to post assignment solutions publicly online? Most of the online courses I've taken prohibit that. I tried to look into it, but without iTunes installed, I can't. \$\endgroup\$ – GlenPeterson May 28 '15 at 12:43
  • \$\begingroup\$ @GlenPeterson I'm not really sure, but I don't think it is prohibited. It is not MOOC like Coursera or edX. Those are just videos and materials from Stanfords course. You don't have any peer review or due dates there. \$\endgroup\$ – Sebastian Osiński May 28 '15 at 12:50
  • \$\begingroup\$ does anyone know where we can find solution to those assignments? \$\endgroup\$ – Thor Mar 25 '16 at 0:54
1
\$\begingroup\$

You wrote:

In hints for the assignment is written that one can use rangeOfString(substring: String) method, but I don't see how it applies to given problem.

Instead of creating a new @IBAction it is possible to put the code for the decimal point in the existing @IBAction func appendDigit. Then rangeOfString(substring: String) can be used to determine whether or not there is already a decimal point (instead of your userInsertedADot boolean).

Eg - the test for whether there was already a floating point would then be:

if (display.text!.rangeOfString(".") != nil)

So you could incorporate something like:

if userIsInTheMiddleOfTypingANumber {
        if (digit == ".")
            && (display.text!.rangeOfString(".") != nil) {
                return // don't do anything if there is already a "."
        } else etc ...

(You might alternatively use if (display.text!.rangeOfString(".") == nil) depending how you approach the rest of the function. If there is not already a decimal point then you want it added to display.text just as if it were a digit.

Also:-

You wrote:

add Label which shows history of performed operations (history and addToHistory(recordToAddToHistory: String))

Looking at the assignment - the 2015 version which I am looking at does not seem to say anything about addToHistory(recordToAddToHistory: String. I am fairly certain that at this stage of the course the history should be derived instead by adding a function to class CalculatorBrain which returns the history by returning the CalculatorBrain's stack as a string. You could then call that function from your View Controller. Eg - something like:

func history() -> String {return " ".join(opStack.map{"\($0)"})

EDIT: Site member Schule has noted that the Swift syntax has changed slightly since this answer was submitted, joinWithSeperator is now the accepted way to convert an array into a string with a separator.

\$\endgroup\$
  • \$\begingroup\$ Hi, thank you for your reply! Now I see how can I use rangeOfString :). I know that the assignment does not say anything about addToHistory method. I just wanted to indicate which part of my code implements history feature. \$\endgroup\$ – Sebastian Osiński May 31 '15 at 11:14
1
\$\begingroup\$

I'm working on the same assignment and my solution for solving the decimal problem is shown below. It is contained within the touchDigit function:

@IBAction func touchDigit(_ sender: UIButton) {
    let digit = sender.currentTitle!

    if userIsInTheMiddleOfTyping {
        if digit == "." && display.text!.contains(".") {
            return
        } else {
            let textCurrentlyInDisplay = display.text!
            display.text = textCurrentlyInDisplay + digit
        }

    }
    else {
        if digit == "." {
            display.text = "0" + digit
        } else {
            display.text = digit
        }
        userIsInTheMiddleOfTyping = true
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.