From 98d6f3fb67c72a2297d44ea4f7d3ab31b2ede99f Mon Sep 17 00:00:00 2001 From: name-mun Date: Mon, 18 Nov 2024 15:51:16 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20Lever=203=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20-=204=EA=B0=9C=EC=9D=98=20=EA=B0=80?= =?UTF-8?q?=EB=A1=9C=20=EC=8A=A4=ED=83=9D=EB=B7=B0=EA=B0=80=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EC=84=B8=EB=A1=9C=20=EC=8A=A4=ED=83=9D=EB=B7=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Calculate/Calculate/ViewController.swift | 154 +++++++++++++---------- 1 file changed, 88 insertions(+), 66 deletions(-) diff --git a/Calculate/Calculate/ViewController.swift b/Calculate/Calculate/ViewController.swift index 66658bb..836b523 100644 --- a/Calculate/Calculate/ViewController.swift +++ b/Calculate/Calculate/ViewController.swift @@ -9,79 +9,101 @@ import UIKit import SnapKit class ViewController: UIViewController { - - let label = UILabel() - let stackView = UIStackView() - let buttons = [UIButton(), UIButton(), UIButton(), UIButton()] - let buttonDatas = ["7", "8", "9", "+"] - - override func viewDidLoad() { - super.viewDidLoad() - - configureUI() - configureLayout() + + let label = UILabel() + var verticalStackView = UIStackView() + let buttonDatas = [["7", "8", "9", "+"], ["4", "5", "6", "-"], ["1", "2", "3", "*"], ["AC", "0", "=", "/"]] + + override func viewDidLoad() { + super.viewDidLoad() + + configureUI() + configureLayout() + } + + // UI 설정 + private func configureUI() { + view.backgroundColor = .black + + [label, verticalStackView].forEach{ + view.addSubview($0) } - - // UI 설정 - private func configureUI() { - view.backgroundColor = .black - configureLabel() - configureStackView() - - [label, stackView].forEach{ - view.addSubview($0) - } + + configureLabel() + configureVerticalStackView() + } + + // label 스타일 설정 + private func configureLabel() { + label.text = "12345" + label.textColor = .white + label.textAlignment = .right + label.font = .boldSystemFont(ofSize: 60) + } + + // VerticalStackView 스타일 설정 + private func configureVerticalStackView() { + for i in 0..<4 { + let view = makeHorizontalStackView(i) + verticalStackView.addArrangedSubview(view) } - - // label 스타일 설정 - private func configureLabel() { - label.text = "12345" - label.textColor = .white - label.textAlignment = .right - label.font = .boldSystemFont(ofSize: 60) + + verticalStackView.axis = .vertical + verticalStackView.backgroundColor = .black + verticalStackView.spacing = 10 + verticalStackView.distribution = .fillEqually + } + + // horizontalStackView 생성 + private func makeHorizontalStackView(_ verIndex: Int) -> UIStackView { + let stackview = UIStackView() + + stackview.axis = .horizontal + stackview.backgroundColor = .black + stackview.spacing = 10 + stackview.distribution = .fillEqually + + for i in 0..<4 { + stackview.addArrangedSubview(makeButton(verIndex: verIndex, horIndex: i)) + } + + stackview.snp.makeConstraints() { + $0.width.equalTo(350) } - - private func configureStackView() { - stackView.axis = .horizontal - stackView.backgroundColor = .black - stackView.spacing = 10 - stackView.distribution = .fillEqually - - configureButton() + + return stackview + } + + // 버튼 생성 + private func makeButton(verIndex: Int, horIndex: Int) -> UIButton { + let keypad = UIButton() + + keypad.setTitle(buttonDatas[verIndex][horIndex], for: .normal) + keypad.titleLabel?.font = .boldSystemFont(ofSize: 30) + keypad.backgroundColor = UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0) + + keypad.snp.makeConstraints() { + $0.width.height.equalTo(80) } - - private func configureButton() { - for i in buttons.indices { - buttons[i].setTitle(buttonDatas[i], for: .normal) - buttons[i].titleLabel?.font = .boldSystemFont(ofSize: 30) - buttons[i].backgroundColor = UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0) - stackView.addArrangedSubview(buttons[i]) - } + + return keypad + } + + // 레이아웃 설정 + private func configureLayout() { + label.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(30) + $0.top.equalToSuperview().inset(200) + $0.height.equalTo(100) } - - // 레이아웃 설정 - private func configureLayout() { - label.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(30) - $0.top.equalToSuperview().inset(200) - $0.height.equalTo(100) - } - - stackView.snp.makeConstraints { - $0.height.equalTo(80) - $0.top.equalTo(label.snp.bottom).offset(50) - $0.centerX.equalToSuperview() - } - - for button in buttons { - button.snp.makeConstraints { - $0.height.width.equalTo(80) - } - } - + + verticalStackView.snp.makeConstraints { + $0.top.equalTo(label.snp.bottom).offset(60) + $0.centerX.equalToSuperview() } + } } #Preview("ViewController") { - ViewController() + ViewController() } From ebe19fee781b8df1318497becc22b4ea29b42cde Mon Sep 17 00:00:00 2001 From: name-mun Date: Mon, 18 Nov 2024 16:09:00 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=A8=20Level=204=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20-=20=EC=97=B0=EC=82=B0=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Calculate/Calculate/ViewController.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Calculate/Calculate/ViewController.swift b/Calculate/Calculate/ViewController.swift index 836b523..29b37eb 100644 --- a/Calculate/Calculate/ViewController.swift +++ b/Calculate/Calculate/ViewController.swift @@ -77,10 +77,14 @@ class ViewController: UIViewController { // 버튼 생성 private func makeButton(verIndex: Int, horIndex: Int) -> UIButton { let keypad = UIButton() + let title = buttonDatas[verIndex][horIndex] + let color = Int(title) == nil ? .orange : UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0) - keypad.setTitle(buttonDatas[verIndex][horIndex], for: .normal) + keypad.setTitle(title, for: .normal) keypad.titleLabel?.font = .boldSystemFont(ofSize: 30) - keypad.backgroundColor = UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0) + + + keypad.backgroundColor = color keypad.snp.makeConstraints() { $0.width.height.equalTo(80) From aad553bb9728daa449179f889032295a6bdf72c5 Mon Sep 17 00:00:00 2001 From: name-mun Date: Mon, 18 Nov 2024 16:28:47 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9C=A8=20Level=205=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20-=20=EB=B2=84=ED=8A=BC=20UI=20=EC=9B=90?= =?UTF-8?q?=ED=98=95=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Calculate/Calculate/ViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Calculate/Calculate/ViewController.swift b/Calculate/Calculate/ViewController.swift index 29b37eb..078af01 100644 --- a/Calculate/Calculate/ViewController.swift +++ b/Calculate/Calculate/ViewController.swift @@ -82,9 +82,8 @@ class ViewController: UIViewController { keypad.setTitle(title, for: .normal) keypad.titleLabel?.font = .boldSystemFont(ofSize: 30) - - keypad.backgroundColor = color + keypad.layer.cornerRadius = 80 / 2 keypad.snp.makeConstraints() { $0.width.height.equalTo(80) @@ -111,3 +110,4 @@ class ViewController: UIViewController { #Preview("ViewController") { ViewController() } + From 2a91c0676a8ba3d18eacdfc90592b755e95f6726 Mon Sep 17 00:00:00 2001 From: name-mun Date: Tue, 19 Nov 2024 11:19:27 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=E2=9C=A8=20Level=206-7=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20-=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=9D=BC=EB=B2=A8=EC=97=90=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Calculate/Calculate/ViewController.swift | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Calculate/Calculate/ViewController.swift b/Calculate/Calculate/ViewController.swift index 078af01..0a2ea1e 100644 --- a/Calculate/Calculate/ViewController.swift +++ b/Calculate/Calculate/ViewController.swift @@ -10,7 +10,7 @@ import SnapKit class ViewController: UIViewController { - let label = UILabel() + var label = UILabel() var verticalStackView = UIStackView() let buttonDatas = [["7", "8", "9", "+"], ["4", "5", "6", "-"], ["1", "2", "3", "*"], ["AC", "0", "=", "/"]] @@ -35,7 +35,7 @@ class ViewController: UIViewController { // label 스타일 설정 private func configureLabel() { - label.text = "12345" + label.text = "0" label.textColor = .white label.textAlignment = .right label.font = .boldSystemFont(ofSize: 60) @@ -89,9 +89,25 @@ class ViewController: UIViewController { $0.width.height.equalTo(80) } + keypad.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside) + return keypad } + // 버튼 클릭시 실행 + @objc + func tappedButton(_ sender: UIButton) { + if sender.currentTitle == "AC" { + label.text = "0" + } else { + if label.text == "0" { + label.text = sender.currentTitle + } else { + label.text! += sender.currentTitle! + } + } + } + // 레이아웃 설정 private func configureLayout() { label.snp.makeConstraints { From 53cb9e1bcd612952ee90ede397f593672e4fcb28 Mon Sep 17 00:00:00 2001 From: name-mun Date: Wed, 20 Nov 2024 20:08:48 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=A8=20Level=208=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20-=20calculate=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EC=83=81=ED=99=A9=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Calculate/Calculate/ViewController.swift | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Calculate/Calculate/ViewController.swift b/Calculate/Calculate/ViewController.swift index 0a2ea1e..2d3add4 100644 --- a/Calculate/Calculate/ViewController.swift +++ b/Calculate/Calculate/ViewController.swift @@ -99,6 +99,11 @@ class ViewController: UIViewController { func tappedButton(_ sender: UIButton) { if sender.currentTitle == "AC" { label.text = "0" + } else if sender.currentTitle == "=" { + guard let result = calculate(expression: label.text!) else { + return + } + label.text = "\(result)" } else { if label.text == "0" { label.text = sender.currentTitle @@ -108,6 +113,41 @@ class ViewController: UIViewController { } } + // 값 계산 + func calculate(expression: String) -> Int? { + // expression이 올바른 수식인지 확인 + guard chechValidText(expression) == true else { + return nil + } + + let expression = NSExpression(format: expression) + if let result = expression.expressionValue(with: nil, context: nil) as? Int { + return result + } else { + return nil + } + } + + // 올바른 수식인지 확인 + private func chechValidText(_ text: String) -> Bool { + let textArray = Array(text) + // 최근 값 저장 + var string = [""] + + for i in textArray.indices { + let thisString = String(textArray[i]) + string.append(thisString) + + if Int(thisString) == nil { + if thisString == string[string.count - 2] || (thisString != "-" && i == 0) || i == text.count - 1 { + return false + } + } + } + + return true + } + // 레이아웃 설정 private func configureLayout() { label.snp.makeConstraints { @@ -123,6 +163,8 @@ class ViewController: UIViewController { } } + + #Preview("ViewController") { ViewController() } From c1a2cb4b2c8c61e1f190623177d19f06151b0cf2 Mon Sep 17 00:00:00 2001 From: mun <77603287+name-mun@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:19:00 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=93=9D=20README.md=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 125 +++++++++++++++++++++++------------------------------- 1 file changed, 53 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 9106619..44eb059 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,55 @@ -# 계산기 만들기 과제 (Week 3-4) - -Swift와 Xcode를 활용해 간단한 계산기 앱을 개발합니다. 이 과제는 Swift 문법을 바탕으로 Playground에서 구현한 로직을 UI와 통합해 실제 앱으로 구현하는 경험을 목표로 합니다. - -## 📝 협업 규칙 - -### Pull Request 작성 규칙 -1. **형식**: `[레벨] 작업 내용 - 팀원 이름` - - 예: `[Lv_1] 라벨 UI 구현 - 홍길동` -2. **작업 세부 사항**: 구현한 주요 기능과 로직에 대한 요약을 작성합니다. - -### 레포지토리 설정 및 브랜치 관리 -1. **Fork로 가져오기**: 각 팀원은 레포지토리를 Fork하여 자신의 개인 레포지토리로 가져옵니다. -2. **브랜치 생성**: Fork한 개인 레포지토리에서 각자의 이름을 딴 브랜치를 생성합니다. -3. **Pull Request**: 각자의 브랜치에서 Pull Request를 생성해 코드 리뷰를 요청합니다. 모든 팀원이 Pull Request에 코멘트를 추가하여 피드백을 제공합니다. -4. **수정 및 Merge**: 피드백을 반영하여 수정한 후, 팀원들의 동의를 얻어 merge를 진행합니다. - --> 풀 리퀘스트를 한 후 Merge하지 않은채 커밋-푸시를 하면 기존 풀 리퀘스트에 들어가기 때문에 그럴 경우 새로운 브랜치를 만듭니다. (ex. Jamong-Lv1, Jamong-Lv2 ...) - -## 📂 코드 파일 구조 - -- **CalculatorApp**: 프로젝트의 메인 진입점이며, SwiftUI로 구현된 인터페이스를 통해 계산기 앱이 실행됩니다. - - **Main.storyboard**: 앱의 기본 UI 구성과 레이아웃을 설정하는 스토리보드 파일입니다. - - **CalculatorViewController.swift**: 계산기의 주요 기능을 구현한 뷰 컨트롤러 파일입니다. - - **Extensions**: UIView와 UIButton에 필요한 공통 설정 및 기능 확장을 모아둔 파일입니다. - - **Utilities**: 계산 로직을 처리하는 헬퍼 메서드를 포함한 파일로, Swift의 `NSExpression`을 활용한 수식 계산 메서드가 구현되어 있습니다. - -## 🌟 필수 구현 기능 (Levels 1-5) - -- **Level 1**: `UILabel`을 사용해 수식을 표시하는 UI를 구현합니다. -- **Level 2**: `UIStackView`를 이용하여 숫자 및 연산 버튼을 구성하는 가로 스택 뷰를 생성합니다. -- **Level 3**: 세로 스택 뷰로 전체 버튼을 배열하여 계산기의 전반적인 UI를 완성합니다. -- **Level 4**: 연산 버튼의 색상을 오렌지로 설정해 차별화합니다. -- **Level 5**: 버튼을 원형으로 만들기 위해 `cornerRadius` 속성을 조정합니다. - -## 💪 도전 구현 기능 (Levels 6-8) - -- **Level 6**: 버튼 클릭 시 라벨에 숫자와 연산 기호가 차례로 표시되도록 구현합니다. -- **Level 7**: `AC` 버튼 클릭 시 초기화되어 기본 값 `0`이 표시되도록 구현합니다. -- **Level 8**: `=` 버튼을 클릭하면 수식이 계산되어 결과가 라벨에 표시되도록 구현합니다. - -## 📜 구현 가이드 - -- **CalculatorViewController.swift** - 각 레벨에 따라 구현된 기능을 `CalculatorViewController.swift` 파일에 추가하여 기본 UI와 로직을 통합합니다. - -```swift -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 - } -} +# iOS 계산기 어플 +![Simulator Screen Recording - iPhone 16 Pro - 2024-11-21 at 13 15 02](https://github.com/user-attachments/assets/cf2f83cd-7c56-4300-9fb0-1d68e934e97e) + +## 목차 +1. [프로젝트 소개](#star-프로젝트-소개) +2. [개발 기간](#calendar-개발기간) +3. [기술스택](#hammer_and_wrench-기술스택) +5. [주요기능](#sparkles-주요기능) +6. [개발 환경](#computer-개발-환경) +7. [설치 및 실행 방법](#inbox_tray-설치-및-실행-방법) +8. [트러블 슈팅](#bug-트러블-슈팅) + + +## :star: 프로젝트 소개 +이 프로젝트는 내일배움캠프 4주차 과제로, iOS 개발 기초를 다지기 위해 제작한 **간단한 계산기 어플**입니다. 사용자가 입력한 값을 바탕으로 기본 연산을 수행합니다. +

+## :calendar: 개발기간 +- 2024.11.14.(목) ~ 2024.11.21(목) +

+## :hammer_and_wrench: 기술스택 + +### :building_construction: 아키텍처 +- MVC + +### :art: UI Framworks +- UIKit +- AutoLayout +

+## :sparkles: 주요기능 +- **기본 연산 기능**: 덧셈, 뺄셈, 곱셈, 나눗셈 수행 +- **입력값 에러 처리**: 잘못된 입력값에 대한 에러 처리 +

+## :computer: 개발 환경 +- **Xcode**: 16.1 +- **iOS Deployment Target**: iOS 18.1 +- **iOS Tested Version**: iOS 18.1 (시뮬레이터 및 실제 기기) +

+## :inbox_tray: 설치 및 실행 방법 +1. 이 저장소를 클론합니다. +```bash +git clone https://github.com/name-mun/sparta-ios-project-w4.git ``` +2. 프로젝트 디렉토리로 이동합니다. +```bash +cd sparta-ios-project-w4 -- **버튼 및 라벨 설정** - - 버튼의 색상, 크기, 모양을 설정하고 라벨에 표시될 수식을 업데이트합니다. - ---- - -## 🎯 목표 - -- **기한**: 11월 22일 (금) 낮 12시까지 제출 -- **제출물**: 개인 과제 결과물을 GitHub에 올리고 링크를 제출합니다. - -## 🔗 참고 링크 -- [Swift 기초 및 iOS 개발 환경 설정](https://developer.apple.com/swift/) -- [Auto Layout 사용 가이드](https://developer.apple.com/documentation/uikit/auto_layout/) - ---- - -이번 과제를 통해 UI와 로직의 통합 구현을 연습하고, 협업을 통한 코드 리뷰와 피드백을 통해 더 나은 코드 품질을 만들어 봅시다. +``` +3. Xcode에서 `sparta-ios-project-w4.xcodeproj` 파일을 엽니다. + +4. Xcode에서 빌드 후 실행합니다. +- 실행 대상에서 **iPhone Simulator** 선택 +- **Cmd + R**로 실행 +

+## :bug: 트러블 슈팅 +👉 [NSExpression format 크래시](https://name-mun.tistory.com/38) From 6069fbe2f419ff138eeeb4b53400ea6cd08cc89c Mon Sep 17 00:00:00 2001 From: name-mun Date: Thu, 21 Nov 2024 18:07:23 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20MVC=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Calculate/Calculate/CalculatorModel.swift | 51 +++++++ Calculate/Calculate/CalculatorView.swift | 117 ++++++++++++++++ Calculate/Calculate/ViewController.swift | 157 ++++------------------ 3 files changed, 193 insertions(+), 132 deletions(-) create mode 100644 Calculate/Calculate/CalculatorModel.swift create mode 100644 Calculate/Calculate/CalculatorView.swift diff --git a/Calculate/Calculate/CalculatorModel.swift b/Calculate/Calculate/CalculatorModel.swift new file mode 100644 index 0000000..95e669e --- /dev/null +++ b/Calculate/Calculate/CalculatorModel.swift @@ -0,0 +1,51 @@ +// +// CalculatorModel.swift +// Calculate +// +// Created by mun on 11/21/24. +// +import Foundation + +class CalculatorModel { + + // 계산된 결과 반환 + func calculate(expression: String) -> Int? { + guard isValidExpression(expression) else { return nil } + + let expression = NSExpression(format: expression) + if let result = expression.expressionValue(with: nil, context: nil) as? Int { + return result + } else { + return nil + } + } + + // 수식이 유효한지 확인 + private func isValidExpression(_ text: String) -> Bool { + let textArray = Array(text) + var previousChar: String? + + // 시작 문자가 "-"을 제외한 다른 연산 기호라면 false + if Int(String(textArray.first!)) == nil && textArray.first != "-" { + return false + } + + // 마지막 문자가 연산 기호라면 false + if Int(String(textArray.last!)) == nil { + return false + } + + for char in textArray { + let currentChar = String(char) + + if Int(currentChar) == nil { + // 연산 기호가 연속으로 나오면 false + if currentChar == previousChar { + return false + } + } + } + + return true + } +} diff --git a/Calculate/Calculate/CalculatorView.swift b/Calculate/Calculate/CalculatorView.swift new file mode 100644 index 0000000..bd125e2 --- /dev/null +++ b/Calculate/Calculate/CalculatorView.swift @@ -0,0 +1,117 @@ +// +// CalculatorView.swift +// Calculate +// +// Created by mun on 11/21/24. +// + +import UIKit + +import SnapKit + +class CalculatorView: UIView { + + var label = UILabel() + var verticalStackView = UIStackView() + + let buttonDatas = [["7", "8", "9", "+"], + ["4", "5", "6", "-"], + ["1", "2", "3", "*"], + ["AC", "0", "=", "/"]] + + override init(frame: CGRect) { + super.init(frame: frame) + configureUI() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // UI 설정 + private func configureUI() { + self.backgroundColor = .black + [label, verticalStackView].forEach() { + self.addSubview($0) + } + + configureLabel() + configureVerticalStackView() + } + + // label 스타일 설정 + private func configureLabel() { + label.text = "0" + label.textColor = .white + label.textAlignment = .right + label.font = .boldSystemFont(ofSize: 60) + } + + // VerticalStackView 스타일 설정 + private func configureVerticalStackView() { + for i in 0..<4 { + let view = makeHorizontalStackView(i) + verticalStackView.addArrangedSubview(view) + } + + verticalStackView.axis = .vertical + verticalStackView.backgroundColor = .black + verticalStackView.spacing = 10 + verticalStackView.distribution = .fillEqually + } + + // horizontalStackView 생성 + private func makeHorizontalStackView(_ verIndex: Int) -> UIStackView { + let horizontalStackView = UIStackView() + + horizontalStackView.axis = .horizontal + horizontalStackView.backgroundColor = .black + horizontalStackView.spacing = 10 + horizontalStackView.distribution = .fillEqually + + for horIndex in 0..<4 { + let button = makeButton(verIndex: verIndex, horIndex: horIndex) + horizontalStackView.addArrangedSubview(button) + } + + return horizontalStackView + } + + // button 생성 + private func makeButton(verIndex: Int, horIndex: Int) -> UIButton { + let button = UIButton() + let title = buttonDatas[verIndex][horIndex] + let color = Int(title) == nil ? .orange : UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0) + + button.setTitle(title, for: .normal) + button.titleLabel?.font = .boldSystemFont(ofSize: 30) + button.backgroundColor = color + button.layer.cornerRadius = 80 / 2 + + button.snp.makeConstraints() { + $0.width.height.equalTo(80) + } + + return button + } + + // button 텍스트 변경 + func setLabelText(_ text: String) { + label.text = text + } + + // 오토 레이아웃 설정 + private func configureLayout() { + label.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(30) + $0.top.equalToSuperview().inset(200) + $0.height.equalTo(100) + } + + verticalStackView.snp.makeConstraints { + $0.top.equalTo(label.snp.bottom).offset(60) + $0.centerX.equalToSuperview() + } + } +} diff --git a/Calculate/Calculate/ViewController.swift b/Calculate/Calculate/ViewController.swift index 2d3add4..6e5f7f5 100644 --- a/Calculate/Calculate/ViewController.swift +++ b/Calculate/Calculate/ViewController.swift @@ -10,162 +10,55 @@ import SnapKit class ViewController: UIViewController { - var label = UILabel() - var verticalStackView = UIStackView() - let buttonDatas = [["7", "8", "9", "+"], ["4", "5", "6", "-"], ["1", "2", "3", "*"], ["AC", "0", "=", "/"]] + var calculatorModel: CalculatorModel! + var calculatorView: CalculatorView! override func viewDidLoad() { super.viewDidLoad() - configureUI() - configureLayout() - } - - // UI 설정 - private func configureUI() { - view.backgroundColor = .black - - [label, verticalStackView].forEach{ - view.addSubview($0) - } - - configureLabel() - configureVerticalStackView() - } - - // label 스타일 설정 - private func configureLabel() { - label.text = "0" - label.textColor = .white - label.textAlignment = .right - label.font = .boldSystemFont(ofSize: 60) - } - - // VerticalStackView 스타일 설정 - private func configureVerticalStackView() { - for i in 0..<4 { - let view = makeHorizontalStackView(i) - verticalStackView.addArrangedSubview(view) - } + calculatorModel = CalculatorModel() + calculatorView = CalculatorView(frame: view.bounds) - verticalStackView.axis = .vertical - verticalStackView.backgroundColor = .black - verticalStackView.spacing = 10 - verticalStackView.distribution = .fillEqually - } - - // horizontalStackView 생성 - private func makeHorizontalStackView(_ verIndex: Int) -> UIStackView { - let stackview = UIStackView() - - stackview.axis = .horizontal - stackview.backgroundColor = .black - stackview.spacing = 10 - stackview.distribution = .fillEqually - - for i in 0..<4 { - stackview.addArrangedSubview(makeButton(verIndex: verIndex, horIndex: i)) - } + view.addSubview(calculatorView) - stackview.snp.makeConstraints() { - $0.width.equalTo(350) - } - - return stackview + configureActions() } - // 버튼 생성 - private func makeButton(verIndex: Int, horIndex: Int) -> UIButton { - let keypad = UIButton() - let title = buttonDatas[verIndex][horIndex] - let color = Int(title) == nil ? .orange : UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0) + // 버튼에 action 연결 + private func configureActions() { + for stackView in calculatorView.verticalStackView.arrangedSubviews { + let stackView = stackView as! UIStackView - keypad.setTitle(title, for: .normal) - keypad.titleLabel?.font = .boldSystemFont(ofSize: 30) - keypad.backgroundColor = color - keypad.layer.cornerRadius = 80 / 2 + for button in stackView.arrangedSubviews { + let button = button as! UIButton - keypad.snp.makeConstraints() { - $0.width.height.equalTo(80) + button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + } } - - keypad.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside) - - return keypad } // 버튼 클릭시 실행 @objc - func tappedButton(_ sender: UIButton) { - if sender.currentTitle == "AC" { - label.text = "0" - } else if sender.currentTitle == "=" { - guard let result = calculate(expression: label.text!) else { - return + func buttonTapped(_ sender: UIButton) { + let buttonTitle = sender.currentTitle! + + if buttonTitle == "AC" { + calculatorView.setLabelText("0") + } else if buttonTitle == "=" { + if let result = calculatorModel.calculate(expression: calculatorView.label.text!) { + calculatorView.setLabelText("\(result)") } - label.text = "\(result)" } else { - if label.text == "0" { - label.text = sender.currentTitle + if calculatorView.label.text == "0" { + calculatorView.setLabelText(buttonTitle) } else { - label.text! += sender.currentTitle! + calculatorView.setLabelText(calculatorView.label.text! + buttonTitle) } } } - - // 값 계산 - func calculate(expression: String) -> Int? { - // expression이 올바른 수식인지 확인 - guard chechValidText(expression) == true else { - return nil - } - - let expression = NSExpression(format: expression) - if let result = expression.expressionValue(with: nil, context: nil) as? Int { - return result - } else { - return nil - } - } - - // 올바른 수식인지 확인 - private func chechValidText(_ text: String) -> Bool { - let textArray = Array(text) - // 최근 값 저장 - var string = [""] - - for i in textArray.indices { - let thisString = String(textArray[i]) - string.append(thisString) - - if Int(thisString) == nil { - if thisString == string[string.count - 2] || (thisString != "-" && i == 0) || i == text.count - 1 { - return false - } - } - } - - return true - } - - // 레이아웃 설정 - private func configureLayout() { - label.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(30) - $0.top.equalToSuperview().inset(200) - $0.height.equalTo(100) - } - - verticalStackView.snp.makeConstraints { - $0.top.equalTo(label.snp.bottom).offset(60) - $0.centerX.equalToSuperview() - } - } } - #Preview("ViewController") { ViewController() } -