Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bd2ae01
feat: 1-9 버튼 ViewController와 연결 - IBAction 생성
vanism2091 Jan 25, 2023
29ce91e
feat: 사칙연산 버튼 ViewController와 연결 - IBAction 생성
zhunhe Jan 25, 2023
c10cf31
feat: equals, clear 버튼 ViewController와 연결
vanism2091 Jan 25, 2023
a8c4cc1
feat: "0", "00", "." 버튼 ViewController와 연결
zhunhe Jan 25, 2023
0ce2192
feat: operator label, number label 연결 및 operator label touch 시 label 설정
vanism2091 Jan 25, 2023
1cd76be
feat: "1" ~ "9" 버튼 동작 구현
zhunhe Jan 25, 2023
936562b
feat: ⁺⁄₋ 버튼 동작 구현
zhunhe Jan 25, 2023
5bcdcc9
feat: initializeCurrentDisplay 구현 및 CE 구현
vanism2091 Jan 25, 2023
80df8ab
feat: "0", "00", "." 버튼 구현
zhunhe Jan 25, 2023
014a591
feat: stackView dynamic append 구현
vanism2091 Jan 25, 2023
ca80f68
refactor: ViewController 이름 변경
zhunhe Jan 25, 2023
471ed40
feat: UIScroll - scroll to bottom 구현
vanism2091 Jan 25, 2023
19e91ae
refactor: UILabel 변수 명 변경, 매직 넘버 삭제, isNumberTyped 삭제
vanism2091 Jan 26, 2023
63b8f52
fix: 소수점 관련 제대로 토글되지 않는 문제 수정
vanism2091 Jan 26, 2023
446cb53
refactor: 계산기 버튼 관련 매직스트링 사용하지 않도록 수정
vanism2091 Jan 26, 2023
4135218
refactor: computed property - isEntryNumberZeroOnly 추가
vanism2091 Jan 26, 2023
652a5e6
feat: AC, CE 구현중
vanism2091 Jan 26, 2023
fa8010a
feat: HistoryStackView, HistoryLabel - CustomView 구현
vanism2091 Jan 26, 2023
5f373c7
feat: result 제외 equal 구현
vanism2091 Jan 27, 2023
7c1e761
feat: equalsDidTap, calcuationResult 구현, Formula resul의 반환 값 변경
vanism2091 Jan 27, 2023
cd8c457
fix: digitDidTap - 연산 결과 표시 후 숫자를 누르면 새로운 숫자가 눌리게 수정
vanism2091 Jan 27, 2023
472df48
chore: 사용하지 않는 메서드 buildDisplayLabel 삭제
vanism2091 Jan 27, 2023
b5ee9af
feat: clearAll History stack 지우기 구현
vanism2091 Jan 27, 2023
512b26c
style: UIScrollView+Extension.swift indent 수정
vanism2091 Jan 27, 2023
7c9fb3d
feat: 사용자가 숫자를 입력중인지 아닌지를 확인할 수 있는 변수 isNumberInTyping 구현, displayNum…
vanism2091 Jan 27, 2023
bc1b781
refactor: Formula-result 내 변수명 변경
vanism2091 Jan 27, 2023
dca6236
refactor: zeroOrPointDidTap 메서드 리팩토링
vanism2091 Jan 27, 2023
d63b674
refactor: 접근 제어 private 적용
vanism2091 Jan 27, 2023
7b045b0
feat: NumberFormatter 추가
vanism2091 Jan 27, 2023
1e5632f
style: 공백 없애기 등
vanism2091 Jan 27, 2023
293f4f1
fix: Not a number 일 오류 때 수정
vanism2091 Jan 27, 2023
405b0f3
refactor: hasDisplayNumberDecimalPlaces, viewDidLoad 삭제
vanism2091 Feb 2, 2023
c60a383
refactor: ScrollView 내 StackView에 사용하지 않는 element 삭제
vanism2091 Feb 2, 2023
f144b84
refactor: computed property의 get, set 에 개행 삽입
vanism2091 Feb 2, 2023
630afc2
refactor: IBAction method 이름에 UIView Component (Button) 삽입
vanism2091 Feb 2, 2023
207c278
refactor: { return } 을 모두 개행 처리
vanism2091 Feb 2, 2023
a55e550
refactor: view controller와 연결된 method renaming 반영
vanism2091 Feb 2, 2023
20588fb
refactor: 1. View와 controller의 분리 2. 변수 naming 3. string literal 제거
vanism2091 Feb 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions Calculator/Calculator.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
407CCAC2297A521300E73142 /* CalculatorItemQueueProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 407CCAC1297A521300E73142 /* CalculatorItemQueueProtocol.swift */; };
40A385A7297934780030787D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A385A6297934780030787D /* Extensions.swift */; };
40A49F16297A1DA000208614 /* FormulaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21D83E02976868700E27BCA /* FormulaTests.swift */; };
40C5EB5C29836C2000BC49A5 /* UIStackView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C5EB5B29836C2000BC49A5 /* UIStackView+Extension.swift */; };
40FF53EE298126AA00C9CA61 /* UIScrollView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FF53ED298126AA00C9CA61 /* UIScrollView+Extension.swift */; };
40FF54142982343000C9CA61 /* HistoryStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FF54132982343000C9CA61 /* HistoryStackView.swift */; };
40FF54162982354A00C9CA61 /* HistoryLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FF54152982354A00C9CA61 /* HistoryLabel.swift */; };
B21D83D62976311000E27BCA /* CalculatorItemQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21D83D52976311000E27BCA /* CalculatorItemQueueTests.swift */; };
B21D83DD29763B2200E27BCA /* Operator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21D83DC29763B2200E27BCA /* Operator.swift */; };
B21D83E3297687ED00E27BCA /* Formula.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21D83E2297687ED00E27BCA /* Formula.swift */; };
Expand All @@ -23,7 +27,7 @@
BFF4D345297A288A00A48817 /* CalculatorModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF4D344297A288A00A48817 /* CalculatorModelTests.swift */; };
C713D9422570E5EB001C3AFC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C713D9412570E5EB001C3AFC /* AppDelegate.swift */; };
C713D9442570E5EB001C3AFC /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C713D9432570E5EB001C3AFC /* SceneDelegate.swift */; };
C713D9462570E5EB001C3AFC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C713D9452570E5EB001C3AFC /* ViewController.swift */; };
C713D9462570E5EB001C3AFC /* CalculatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C713D9452570E5EB001C3AFC /* CalculatorViewController.swift */; };
C713D9492570E5EB001C3AFC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C713D9472570E5EB001C3AFC /* Main.storyboard */; };
C713D94B2570E5ED001C3AFC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C713D94A2570E5ED001C3AFC /* Assets.xcassets */; };
C713D94E2570E5ED001C3AFC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C713D94C2570E5ED001C3AFC /* LaunchScreen.storyboard */; };
Expand All @@ -42,6 +46,10 @@
/* Begin PBXFileReference section */
407CCAC1297A521300E73142 /* CalculatorItemQueueProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalculatorItemQueueProtocol.swift; sourceTree = "<group>"; };
40A385A6297934780030787D /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
40C5EB5B29836C2000BC49A5 /* UIStackView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+Extension.swift"; sourceTree = "<group>"; };
40FF53ED298126AA00C9CA61 /* UIScrollView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Extension.swift"; sourceTree = "<group>"; };
40FF54132982343000C9CA61 /* HistoryStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryStackView.swift; sourceTree = "<group>"; };
40FF54152982354A00C9CA61 /* HistoryLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryLabel.swift; sourceTree = "<group>"; };
B21D83D32976311000E27BCA /* CalculatorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CalculatorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
B21D83D52976311000E27BCA /* CalculatorItemQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalculatorItemQueueTests.swift; sourceTree = "<group>"; };
B21D83DC29763B2200E27BCA /* Operator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operator.swift; sourceTree = "<group>"; };
Expand All @@ -58,7 +66,7 @@
C713D93E2570E5EB001C3AFC /* Calculator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Calculator.app; sourceTree = BUILT_PRODUCTS_DIR; };
C713D9412570E5EB001C3AFC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C713D9432570E5EB001C3AFC /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
C713D9452570E5EB001C3AFC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
C713D9452570E5EB001C3AFC /* CalculatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalculatorViewController.swift; sourceTree = "<group>"; };
C713D9482570E5EB001C3AFC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
C713D94A2570E5ED001C3AFC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C713D94D2570E5ED001C3AFC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -101,7 +109,7 @@
children = (
C713D9412570E5EB001C3AFC /* AppDelegate.swift */,
C713D9432570E5EB001C3AFC /* SceneDelegate.swift */,
C713D9452570E5EB001C3AFC /* ViewController.swift */,
C713D9452570E5EB001C3AFC /* CalculatorViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
Expand All @@ -111,6 +119,8 @@
children = (
C713D9472570E5EB001C3AFC /* Main.storyboard */,
C713D94C2570E5ED001C3AFC /* LaunchScreen.storyboard */,
40FF54132982343000C9CA61 /* HistoryStackView.swift */,
40FF54152982354A00C9CA61 /* HistoryLabel.swift */,
);
path = View;
sourceTree = "<group>";
Expand All @@ -131,6 +141,8 @@
children = (
BF9AE301297658C00021D14E /* Double+Extension.swift */,
BF283A7E2978D58E005DD048 /* String+Extension.swift */,
40FF53ED298126AA00C9CA61 /* UIScrollView+Extension.swift */,
40C5EB5B29836C2000BC49A5 /* UIStackView+Extension.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -311,15 +323,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C713D9462570E5EB001C3AFC /* ViewController.swift in Sources */,
C713D9462570E5EB001C3AFC /* CalculatorViewController.swift in Sources */,
BFEE745A2977D1DE00ACB03E /* ExpressionParser.swift in Sources */,
C713D9422570E5EB001C3AFC /* AppDelegate.swift in Sources */,
B21D83DD29763B2200E27BCA /* Operator.swift in Sources */,
BF9AE302297658C00021D14E /* Double+Extension.swift in Sources */,
BF9AE30529765ABB0021D14E /* CalculateItem.swift in Sources */,
BF9AE2F3297635580021D14E /* CalculatorItemQueue.swift in Sources */,
40FF54142982343000C9CA61 /* HistoryStackView.swift in Sources */,
40C5EB5C29836C2000BC49A5 /* UIStackView+Extension.swift in Sources */,
B21D83E3297687ED00E27BCA /* Formula.swift in Sources */,
BF283A7F2978D58E005DD048 /* String+Extension.swift in Sources */,
40FF53EE298126AA00C9CA61 /* UIScrollView+Extension.swift in Sources */,
40FF54162982354A00C9CA61 /* HistoryLabel.swift in Sources */,
C713D9442570E5EB001C3AFC /* SceneDelegate.swift in Sources */,
407CCAC2297A521300E73142 /* CalculatorItemQueueProtocol.swift in Sources */,
);
Expand Down
199 changes: 199 additions & 0 deletions Calculator/Calculator/Controller/CalculatorViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//
// Calculator - ViewController.swift
// Created by yagom.
// Copyright © yagom. All rights reserved.
//

import UIKit

final class CalculatorViewController: UIViewController {

private enum Constant {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상수를 enum으로 정리한 부분 좋습니다 👍

static let zero = "0"
static let doubleZero = "00"
static let dot = "."
static let allClear = "AC"
static let clearEntry = "CE"
static let NotANumber = "NaN"
static let emptyString = ""
static let comma = ","
static let nine = "9"
}

@IBOutlet private weak var operatorLabel: UILabel!
@IBOutlet private weak var entryNumberLabel: UILabel!
@IBOutlet private weak var calculationHistoryScrollView: UIScrollView!
@IBOutlet private weak var calculationHistoryContentView: UIStackView!

private let numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 20
return formatter
}()

private let maxDigitLength = 15
private var isDisplayNumberZeroOnly: Bool {
displayNumber == Constant.zero
}
private var formulaString = Constant.emptyString
private var isTypingNumber = false
private var displayNumber: String = Constant.zero {
willSet {
guard let lastElement = newValue.last else {
return
}
let lastCharacter = String(lastElement)
switch lastCharacter {
case Constant.dot:
entryNumberLabel.text?.append(lastCharacter)
case Constant.zero...Constant.nine:
entryNumberLabel.text = parse(newValue)
default:
entryNumberLabel.text = newValue
}
}
}
private var displayOperator: String = Constant.emptyString {
willSet {
operatorLabel.text = newValue
}
}

@IBAction private func digitButtonDidTap(_ sender: UIButton) {
guard let digit = sender.currentTitle, displayNumber.count < maxDigitLength else {
return
}
if isTypingNumber {
displayNumber += digit
} else {
displayNumber = digit
isTypingNumber = true
}
}

@IBAction private func arithmeticOperatorButtonDidTap(_ sender: UIButton) {
if displayNumber == Constant.NotANumber {
displayNumber = Constant.zero
}
guard let buttonTitle = sender.currentTitle else {
return
}
if isTypingNumber || false == isDisplayNumberZeroOnly {
addCalculationHistory()
}
displayOperator = buttonTitle
clearEntry()
}

@IBAction private func equalsButtonDidTap(_ sender: UIButton) {
guard false == displayOperator.isEmpty else {
return
}
print(formulaString)
addCalculationHistory()
let result = calculationResult(from: formulaString)
print(formulaString, result, displayNumber)
displayNumber = result
clearOperatorAndFormulaString()
isTypingNumber = false
}

@IBAction private func clearButtonDidTap(_ sender: UIButton) {
switch sender.currentTitle {
case Constant.clearEntry:
clearEntry()
case Constant.allClear:
clearAll()
default:
return
}
}

@IBAction private func signToggleButtonDidTap(_ sender: UIButton) {
guard nil != sender.currentTitle,
false == isDisplayNumberZeroOnly,
let number = Double(displayNumber) else {
return
}
displayNumber = String(number * -1)
}

@IBAction private func zeroOrPointButtonDidTap(_ sender: UIButton) {
guard let buttonTitle = sender.currentTitle,
displayNumber.count < maxDigitLength else {
return
}

switch buttonTitle {
case Constant.zero, Constant.doubleZero:
if isDisplayNumberZeroOnly {
return
}
let suffix = (displayNumber + buttonTitle).count > maxDigitLength ? Constant.zero : buttonTitle
displayNumber += suffix
case Constant.dot:
if displayNumber.contains(Constant.dot) {
return
}
displayNumber += buttonTitle
isTypingNumber = true
default:
return
}
}
}

extension CalculatorViewController {
// MARK: CalculationResult
private func calculationResult(from formula: String) -> String {
let result = ExpressionParser.parse(from: formula).result()
switch result {
case .success(let res):
return String(res)
case .failure(let error):
return error.description
}
}

// MARK: Parse - numberFormat
private func parse(_ value: String) -> String {
let removedComma = value.replacingOccurrences(of: Constant.comma, with: Constant.emptyString)
let nsNumber = numberFormatter.number(from: removedComma)
return (numberFormatter.string(for: nsNumber) ?? Constant.zero)
}

// MARK: Add History
private func addCalculationHistory() {
let currOperator = formulaString.isEmpty ? Constant.emptyString : displayOperator
appendHistoryStackView(operator: currOperator)
formulaString += "\(currOperator)\(displayNumber)"
}

private func appendHistoryStackView(operator: String?) {
let parsedNumber = parse(displayNumber)
let stackView = HistoryStackView(operator: `operator`, operand: parsedNumber)
calculationHistoryContentView.addArrangedSubview(stackView)

view.layoutIfNeeded()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

layoutIfNeeded는 왜 호출했나요? 꼭 필요한 코드인가요?

calculationHistoryScrollView.scrollToBottom()
}

// MARK: Clear
private func clearEntry() {
displayNumber = Constant.zero
isTypingNumber = false
}

private func clearOperatorAndFormulaString() {
displayOperator = Constant.emptyString
formulaString = Constant.emptyString
}

private func clearAll() {
clearEntry()
clearOperatorAndFormulaString()
calculationHistoryContentView.removeAllHistorySubviews()
}
}
14 changes: 0 additions & 14 deletions Calculator/Calculator/Controller/ViewController.swift

This file was deleted.

15 changes: 15 additions & 0 deletions Calculator/Calculator/Extensions/UIScrollView+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// UIScrollView+Extension.swift
// Calculator
//
// Created by sei_dev on 1/25/23.
//

import UIKit

extension UIScrollView {
func scrollToBottom() {
let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height + contentInset.bottom)
setContentOffset(bottomOffset, animated: true)
}
}
16 changes: 16 additions & 0 deletions Calculator/Calculator/Extensions/UIStackView+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// UIStackView+Extension.swift
// Calculator
//
// Created by sei_dev on 1/25/23.
//

import UIKit

extension UIStackView {
func removeAllHistorySubviews() {
arrangedSubviews.forEach { history in
history.removeFromSuperview()
}
}
}
25 changes: 17 additions & 8 deletions Calculator/Calculator/Model/Formula.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,40 @@ import Foundation
enum FormulaError: Error {
case dividedByZero
case wrongFormula

var description: String {
switch self {
case .dividedByZero:
return "NaN"
case .wrongFormula:
return "올바르지 않은 식"
}
}
}

struct Formula {
var operands: CalculatorItemQueue<Double>
var operators: CalculatorItemQueue<Operator>
private var operands: CalculatorItemQueue<Double>
private var operators: CalculatorItemQueue<Operator>

init(operands: [Double] = [], operators: [Operator] = []) {
self.operands = CalculatorItemQueue(items: operands)
self.operators = CalculatorItemQueue(items: operators)
}

func result() throws -> Double {
func result() -> Result<Double, FormulaError> {
guard operands.count == operators.count + 1 else {
throw FormulaError.wrongFormula
return .failure(.wrongFormula)
}

let pairs = zip(operands.values.dropFirst(), operators.values)
guard false == pairs.contains(where: { pair in (0.0, Operator.divide) == pair }) else {
throw FormulaError.dividedByZero
return .failure(.dividedByZero)
}

let result = pairs.reduce(operands.values[0]) { (partialResult, pair) in
let (currentOperand, currentOperator) = pair
return currentOperator.calculate(lhs: partialResult, rhs: currentOperand)
let (operand, `operator`) = pair
return `operator`.calculate(lhs: partialResult, rhs: operand)
}
return result
return .success(result)
}
}
Loading