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

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

by UDDT 2025. 4. 2.

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

 

- 내가 작성한 코드

// DataModel.swift

import Foundation

class DataModel {
    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
        }
    }
}
// ViewController.swift

import UIKit

class ViewController: UIViewController {

    let onlyView = OnlyView()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view = onlyView
    }
}
// OnlyView.swift
import UIKit

class OnlyView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setLabel()
        setUI()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    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()

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

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

        self.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false

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

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

        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) -> [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])

        self.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])

        self.addSubview(verticalStackView)
        verticalStackView.translatesAutoresizingMaskIntoConstraints = false

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

        return verticalStackView
    }

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

 

 Alert 추가하기

   구현을 마치고 테스트를 해보던 중, "+-", "--" 등으로 연산을 2번 입력할 수 있다는 것을 발견했다.

이 상태로 "=" 버튼을 누르면 Model에 있는 Calculator 함수에서 오류가 발생한다.

이를 해결하기 위해 예외처리를 하고, Alert을 구현하기로 했다.

Alert

 

   현재 Model / View / Controller가 나눠져 있는 상황이어서

Alert을 Controller에서 구현하고, View에서 실행해주고자 했다.

(View에 연산 메서드가 있기 때문에)

    // Alert 만들어주는 함수
    func makeAlert(_ message: String) {
        let alert = UIAlertController(title: "경고", message: message, preferredStyle: .alert)
        let alertAction = UIAlertAction(title: "확인", style: .default)
        alert.addAction(alertAction)
        self.present(alert, animated: true)
    }

    // Alert의 message를 사용할 때 주입 받기 위한 함수
    func showAlert(_ message: String) {
        makeAlert(message)
    }

 

  함수는 위와 같이 구현을 했는데, 어떻게 데이터를 받아올지 막막해졌다.

연산 메서드는 View에 있고, ViewController에 있는 함수를 실행해야한다.

어떻게 해야할까?

 

  이럴 때 사용하는 것이 Delegate 패턴이다.

  https://uddt.tistory.com/187

 

Swift | Delegate 패턴이 뭔데?

Delegate 패턴   예? 엘리게이터요?    아뇨 델리게이트요!⎮ Delegate가 뭔데?Delegate : (집단의 의사를 대표하는) 대표(자) / 위임하다 / (대표를) 뽑다[선정하다]   https://developer.apple.com/documentation/

uddt.tistory.com

 

 Delegate 패턴 적용하기

    Delegate 패턴을 적용하기 위해 protocol을 만들어줬다.

protocol AlertViewDelegate {
    func showAlert(_ message: String)
}

 

    먼저 View에서 대리자 공고를 내고 protocol로 타입을 지정해줬다

    var delegate: AlertViewDelegate?

 

    대리자 공고를 본 ViewController가 자원을 한다.

 

 

    이렇게 하면 Delegate가 가지고 있는 Alert 창을 띄울 수 있게 된다.

 

 

최근댓글

최근글

skin by © 2024 ttuttak