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="이 되며, 그에 따라 크래시가 발생하는 것이다.
따라서 로직의 순서를 바꿔줘야 한다.
@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의 문제로 연산을 이어서 하지 못한다는 점(곱하기 연산을 선택하면 곱하기가 우선으로 실행되어 잘못된 값이 나옴)
'스파르타코딩 클럽 > 개인과제' 카테고리의 다른 글
18. 스파르타 코딩클럽 [본캠프 - 계산기 앱 : 정수기(8)] (0) | 2025.04.02 |
---|---|
17. 스파르타 코딩클럽 [본캠프 - 계산기 앱 : 정수기(7)] (0) | 2025.04.01 |
15. 스파르타 코딩클럽 [본캠프 - 계산기 앱 : 정수기(5)] (0) | 2025.03.31 |
14. 스파르타 코딩클럽 [본캠프 - 계산기 앱 : 정수기(4)] (0) | 2025.03.29 |
13. 스파르타 코딩클럽 [본캠프 - 계산기 앱 : 정수기(3)] (0) | 2025.03.28 |