본문 바로가기
스파르타코딩 클럽/개인과제

16. 스파르타 코딩클럽 [본캠프 - 계산기 앱 : 정수기(6)]

by UDDT 2025. 4. 1.

Ch 3. 앱 개발 입문 주차 과제

 Lv. 8

Lv.8 등호 (=) 버튼을 클릭하면 연산이 수행되도록 구현합니다.

- 1+2+3 을 입력 후, = 을 클릭하면 결과값인 6 이 출력되도록 해주세요.
- 123*456 을 입력 후, = 을 클릭하면 결과값인 56088 이 출력되도록 해주세요.
- 연산에 필요한 메서드를 제공해드리겠습니다. 이 메서드를 활용해주세요.

  수식 문자열을 넣으면 계산해주는 메서드.
- 예를 들어 expression 에 "1+2+3" 이 들어오면 6 을 리턴한다.
- 잘못된 형식의 수식을 넣으면 앱이 크래시 난다.
/// 수식 문자열을 넣으면 계산해주는 메서드.
///
/// 예를 들어 expression 에 "1+2+3" 이 들어오면 6 을 리턴한다.
/// 잘못된 형식의 수식을 넣으면 앱이 크래시 난다. ex) "1+2++"
func calculate(expression: String) -> Int? {
	    let expression = NSExpression(format: expression)
    if let result = expression.expressionValue(with: nil, context: nil) as? Int {
        return result
    } else {
        return nil
    }
}

예시 이미지

 

 Lv8. 내가 작성한 코드

class ViewController: UIViewController {

    var number = "0"

    let label = UILabel()
    let button = UIButton()

    private var titles = [["7", "8", "9", "+"], ["4", "5", "6", "-"], ["1", "2", "3", "*"], ["AC", "0", "=", "/"]]

    private var verticalStackView = UIStackView()
    private var stackView1 = UIStackView()
    private var stackView2 = UIStackView()
    private var stackView3 = UIStackView()
    private var stackView4 = UIStackView()

    override func viewDidLoad() {
        super.viewDidLoad()
        setLabel()
        setUI()
    }

    private func setLabel() {
        view.backgroundColor = .black

        label.text = "\(number)"
        label.textAlignment = .right
        label.textColor = .white
        label.font = UIFont.boldSystemFont(ofSize: 60)

        view.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
            label.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
            label.heightAnchor.constraint(equalToConstant: 100)
        ])
    }

    func setUI() {
        let setButton1 = setButton(titles[0], #selector(buttonTapped), button)
        let setButton2 = setButton(titles[1], #selector(buttonTapped), button)
        let setButton3 = setButton(titles[2], #selector(buttonTapped), button)
        let setButton4 = setButton(titles[3], #selector(buttonTapped), button)

        stackView1 = makeHorizontalStackView(setButton1)
        stackView2 = makeHorizontalStackView(setButton2)
        stackView3 = makeHorizontalStackView(setButton3)
        stackView4 = makeHorizontalStackView(setButton4)

        let arrStackView = fourStackView()
        verticalStackView = makeVerticalStackView(arrStackView)
    }

    // 타이틀이 바뀌어 적용되는 버튼을 만들고 배열로 묶어주는 함수
    private func setButton(_ titles : [String], _ action: Selector, _ button: UIButton) -> [UIButton] {
        var arrButtons: [UIButton] = []
        let operate = ["+", "-", "*", "AC", "=", "/"]

        for title in titles {
            let button = UIButton()
            button.addTarget(self, action: action, for: .touchDown)
            button.setTitle(title, for: .normal)
            // 만약에 title이 operate의 요소를 포함하고 있지 않으면
            if !operate.contains(title) {
                button.backgroundColor = UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0)
            } else {
                button.backgroundColor = UIColor.orange
            }
            button.titleLabel?.font = .boldSystemFont(ofSize: 30)

            button.layer.cornerRadius = 40
            arrButtons.append(button)
        }
        return arrButtons
    }

    // 4개의 버튼 배열을 묶어서 1개의 스택뷰로 만들어주는 함수
    private func makeHorizontalStackView(_ views: [UIButton]) -> UIStackView {
        let horizontalStackView = UIStackView()
        horizontalStackView.axis = .horizontal
        horizontalStackView.backgroundColor = .black
        horizontalStackView.spacing = 10
        horizontalStackView.distribution = .fillEqually

        horizontalStackView.addArrangedSubview(views[0])
        horizontalStackView.addArrangedSubview(views[1])
        horizontalStackView.addArrangedSubview(views[2])
        horizontalStackView.addArrangedSubview(views[3])

        view.addSubview(horizontalStackView)
        horizontalStackView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            horizontalStackView.heightAnchor.constraint(equalToConstant: 80)
        ])

        return horizontalStackView
    }

    // 4개의 스택뷰를 묶어서 배열로 리턴하는 함수
    private func fourStackView() -> [UIStackView] {
        let arrStackView = [stackView1, stackView2, stackView3, stackView4]
        return arrStackView
    }

    // 스택뷰 배열을 받아서 UIStackView로 리턴하는 함수 (여기서 Vertical StackView로 전환)
    private func makeVerticalStackView(_ stackViews: [UIStackView]) -> UIStackView {
        verticalStackView.axis = .vertical
        verticalStackView.backgroundColor = .black
        verticalStackView.spacing = 10
        verticalStackView.distribution = .fillEqually

        verticalStackView.addArrangedSubview(stackViews[0])
        verticalStackView.addArrangedSubview(stackViews[1])
        verticalStackView.addArrangedSubview(stackViews[2])
        verticalStackView.addArrangedSubview(stackViews[3])

        view.addSubview(verticalStackView)
        verticalStackView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            verticalStackView.widthAnchor.constraint(equalToConstant: 350),
            verticalStackView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 60),
            verticalStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])

        return verticalStackView
    }


    @objc
    private func buttonTapped(_ sender: UIButton) {
        guard let title = sender.currentTitle else { return }
        number += title
        if number.first == "0" {
            number.removeFirst()
        }
        label.text = "\(number)"

        if sender.currentTitle == "=" {
            if let result = calculate(expression: "\(number)") {
                label.text = "\(result)"
            }
        }
    }

    func calculate(expression: String) -> Int? {
            let expression = NSExpression(format: expression)
        if let result = expression.expressionValue(with: nil, context: nil) as? Int {
            return result
        } else {
            return nil
        }
    }
}

 

  위와 같이 작성하고 빌드를 하니 다음과 같은 오류가 생겼다.

 

   이전 레벨과 다르게 추가한 코드는 이 코드이므로,

해당 코드에 문제가 있음을 알 수 있었다.

        if sender.currentTitle == "=" {
            if let result = calculate(expression: "\(number)") {
                label.text = "\(result)"
            }
        }
        
    func calculate(expression: String) -> Int? {
            let expression = NSExpression(format: expression)
        if let result = expression.expressionValue(with: nil, context: nil) as? Int {
            return result
        } else {
            return nil
        }
    }
}

 

     sender.currentTitle(버튼의 title)이 "="일 때만,

calculate 함수를 호출하도록 구현했는데 왜 오류가 생겼을까?

 

 트러블 슈팅

    - calculate 함수의 구조

    func calculate(expression: String) -> Int? {
            let expression = NSExpression(format: expression)
        if let result = expression.expressionValue(with: nil, context: nil) as? Int {
            return result
        } else {
            return nil
        }
    }

 

   먼저 연산을 진행해주는 이 함수의 동작 방식을 확인해야한다.

calculate 함수는 String으로 된 수식(예를 들면, 1+2)을 받아 Int로 변환이 가능하면 result를 반환하는 함수다.

만약 Int로 변환이 불가능한 형태라면 nil을 리턴하게 된다.

 

    - 조건문의 논리

        if sender.currentTitle == "=" {
            if let result = calculate(expression: "\(number)") {
                label.text = "\(result)"
            }
        }

 

   위의 코드는 sender.currentTitle,

 즉 = 버튼이라면 calculate 함수를 실행하는 것이고,

해당 결과를 label.text에 전달하는 조건문이다.

 

 논리 자체는 문제가 없어보이지만,

 이전 코드와 함께 본다면, 한가지 문제가 생긴다.

    @objc
    private func buttonTapped(_ sender: UIButton) {
        guard let title = sender.currentTitle else { return }
        number += title
        if number.first == "0" {
            number.removeFirst()
        }
        label.text = "\(number)"

        if sender.currentTitle == "=" {
            if let result = calculate(expression: "\(number)") {
                label.text = "\(result)"
            }
        }
    }

    func calculate(expression: String) -> Int? {
            let expression = NSExpression(format: expression)
        if let result = expression.expressionValue(with: nil, context: nil) as? Int {
            return result
        } else {
            return nil
        }
    }
}

 

  1버튼을 누르고, + 버튼을 누르고, 2 버튼을 누르고, = 버튼을 누르면

"1+2="이 되며, 그에 따라 크래시가 발생하는 것이다.

1+2=은 Int로 변환이 불가능하기 때문에 nil

  따라서 로직의 순서를 바꿔줘야 한다.

    @objc
    private func buttonTapped(_ sender: UIButton) {
        guard let title = sender.currentTitle else { return }
        if title != "=" {
            number += title
            if number.first == "0" {
                number.removeFirst()
            }
            label.text = "\(number)"
        } else {
            if let result = calculate(expression: "\(number)") {
                label.text = "\(result)"
            }
        }
    }

  

  if문을 통해 title(선택된 버튼의 타이틀)이 "="인지 아닌지 확인하고 해당 값을 저장하도록 만들고,

title이 "="이면 result를 label.text에 담아주도록 반영했다.

 

 

  그러나 이 계산기는 한가지 문제가 있는데,

 

 

   바로 위와 같은 2가지 문제이다.

1. 이전의 이력을 다 가지고 있다는 점

2. 1의 문제로 연산을 이어서 하지 못한다는 점(곱하기 연산을 선택하면 곱하기가 우선으로 실행되어 잘못된 값이 나옴)

최근댓글

최근글

skin by © 2024 ttuttak