1. 처음 시작 코드
- 기본적인 View와 이벤트를 연결한다.
//
// ViewController.swift
// calculatorMVC
//
// Created by Taehoon Kim on 2022/02/01.
//
import UIKit
class ViewController: UIViewController {
// Properties
// 계산기의 계산결과를 보여줄 label 뷰
let display: UILabel = {
let label = UILabel()
label.backgroundColor = .systemBlue
label.textColor = .white
label.textAlignment = .right
label.font = UIFont.systemFont(ofSize: 22)
return label
}()
var userIsIntheMiddleOfTyping = false
override func viewDidLoad() {
super.viewDidLoad()
createUIView()
}
/// displaydhk keypad 버튼을 stackView로 생성하는 함수
func createUIView() {
let keypadStructure = [["*", "/", "+", "-"],
["𝝿", "7", "8", "9"],
["√", "4", "5", "6"],
["save", "1", "2", "3"],
["restore", ".", "0", "="]]
/// 전체적인 스택뷰
let fullOfCalculatorView = UIStackView(arrangedSubviews: [display])
fullOfCalculatorView.axis = .vertical
fullOfCalculatorView.distribution = .fillEqually
fullOfCalculatorView.spacing = 8
keypadStructure.forEach { (keypadNames) in
/// 계산기의 한줄을 맡는 Stackview
let oneOfRowView = UIStackView()
oneOfRowView.axis = .horizontal
oneOfRowView.spacing = 8
oneOfRowView.distribution = .fillEqually
keypadNames.forEach { (keypadName) in
let button = UIButton()
button.setTitle(keypadName, for: .normal)
button.backgroundColor = .systemGray6
button.setTitleColor(.systemBlue, for: .normal)
if (keypadName == "𝝿" || keypadName == "√" || keypadName == "cos" || keypadName == "*" || keypadName == "=" || keypadName == "+" || keypadName == "-" || keypadName == "/") { button.addTarget(self, action: #selector(performOperator(_:)), for: .touchUpInside) }
else { button.addTarget(self, action: #selector(keypadButtonTapped(_:)), for: .touchUpInside) }
oneOfRowView.addArrangedSubview(button)
}
fullOfCalculatorView.addArrangedSubview(oneOfRowView)
}
view.addSubview(fullOfCalculatorView)
fullOfCalculatorView.translatesAutoresizingMaskIntoConstraints = false
fullOfCalculatorView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
fullOfCalculatorView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true
fullOfCalculatorView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -8).isActive = true
fullOfCalculatorView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8).isActive = true
}
// Selector
/// keypad 클릭시 동작하는 함수
@objc func keypadButtonTapped(_ sender: UIButton) {
let digit = sender.currentTitle!
if userIsIntheMiddleOfTyping {
let textCurrentlyInDisplay = display.text!
display.text = textCurrentlyInDisplay + digit
} else {
display.text = digit
}
userIsIntheMiddleOfTyping = true
}
@objc func performOperator(_ sender: UIButton) {
// false로 만들어서 숫자입력시 새롭게 입력받음
userIsIntheMiddleOfTyping = false
if let mathematicalSymbol = sender.currentTitle {
if mathematicalSymbol == "𝝿" {
display.text = String(M_PI)
}
}
}
}
2. Set, Get 연산프로퍼티로 display text 변경
- 계산 결과를 보여주는 display UILabel의 text값은 String으로 설정해야한다.
- 계산 시에는 Double로 계산해야한다.
=> displayValue로 연산프로퍼티를 만들어서 코드를 간결하게 하자
/// 얻어 올땐 Double로 설정 할땐 String으로 넣어주는 get, set을 작성함
/// 연산되는 변수를 작성함
var displayValue: Double {
get {
return Double(display.text!)!
}
set {
display.text = String(newValue)
}
}
@objc func performOperator(_ sender: UIButton) {
// false로 만들어서 숫자입력시 새롭게 입력받음
userIsIntheMiddleOfTyping = false
if let mathematicalSymbol = sender.currentTitle {
if mathematicalSymbol == "𝝿" {
// 이코드를 바꿀 수 있음
displayValue = .pi
} else if mathematicalSymbol == "√" {
displayValue = sqrt(displayValue)
}
}
}
3. Model을 만들자.
performOperator 부분은 앱의 기능이기 때문에 Model에 작성해준다.
+ CalculatorBrain.swift 파일을 추가함
//
// CaculatorBrain.swift
// calculatorMVC
//
// Created by Taehoon Kim on 2022/02/01.
//
import Foundation
class CalculatorBrain
{
/// accumulator(누산기): 계산기에서 누적되는 값
private var accumulator = 0.0
/// operand: 연산의 대상값
func setOperand(_ operand: Double) {
accumulator = operand
}
/// symbol에 따라 연산을 수행하는 함수
func perforOperation(symbol: String) {
switch symbol {
case "𝝿": accumulator = .pi
case "√": accumulator = sqrt(accumulator)
default: break
}
}
/// 사용하는 controller에게 accumulator 값을 전달하는 get함수
var result: Double {
get {
return accumulator
}
}
}
- ViewController 부분 수정
/// 생성한 CalculatorBrain Model 선언
private var brain = CalculatorBrain()
@objc private func performOperator(_ sender: UIButton) {
// 사용자가 숫자를 입력중이라면 숫자값을 설정해주는 함수
if userIsIntheMiddleOfTyping {
brain.setOperand(displayValue)
userIsIntheMiddleOfTyping = false
}
if let mathematicalSymbol = sender.currentTitle {
brain.perforOperation(symbol: mathematicalSymbol)
displayValue = brain.result
}
}
Model에서 dictionary를 사용해 기호-action 으로 정리하자
var operations: Dictionary<String, Double> = [
"𝝿" : .pi,
"e" : M_E,
]
func perforOperation(symbol: String) {
if let constant = operations[symbol] {
accumulator = constant
}
}
enum, switch 를 사용하여 코드를 명확하게하자.
* enum에 값과 함수가 필요하다고 정의하여 담아 전달할수 있다.
var operations: Dictionary<String, Operation> = [
"𝝿" : Operation.Constant(.pi), // pi
"e" : Operation.Constant(M_E), // M_E,
"√" : Operation.UnaryOperation(sqrt), // sqrt
"cos" : Operation.UnaryOperation(cos), // cos
]
enum Operation {
case Constant(Double)
case UnaryOperation((Double) -> Double)
case BinaryOperation
case Equals
}
func perforOperation(symbol: String) {
if let operation = operations[symbol] {
switch operation {
//value - associatedConstantValue
case .Constant(let value): accumulator = value
case .UnaryOperation(let function): accumulator = function(accumulator)
case .BinaryOperation:
break
case .Equals:
break
}
}
}
Struct사용하기
- accumulator가 계속 저장됨을 이용하여 +,-,*,/ 같은 이항연산을 계산 할 수 있다.
- 계산기가 수행해야 할 함수와 기호를 누르기전 입력된 값(accumulator)을 전달 받는 Struct를 생성하여 peding 변수로 저장해준다.
- Equals나 다른 +,-,*,/를 눌렀을 때 pending에 저장된 계산기가 수행해야 할 함수를 실행한다.
func perforOperation(symbol: String) {
if let operation = operations[symbol] {
switch operation {
//value - associatedConstantValue
case .Constant(let value):
accumulator = value
case .UnaryOperation(let function):
accumulator = function(accumulator)
case .BinaryOperation(let function):
executePendingBinaryOperation()
pending = PendingBinaryOperationInfo(binaryFunction: function, firstOperand: accumulator)
case .Equals:
executePendingBinaryOperation()
}
}
}
/// 준비된 이항연산 실행함
private func executePendingBinaryOperation() {
if pending != nil {
accumulator = pending!.binaryFunction(pending!.firstOperand, accumulator)
pending = nil
}
}
private var pending: PendingBinaryOperationInfo?
// Array, Double,Int,String 은 모두 struct, 값복사를 진행함
struct PendingBinaryOperationInfo {
var binaryFunction: (Double, Double) -> Double
var firstOperand: Double
}
Closure 사용하기
- 함수를 보다 쉽게 표현 할 수 있다.
- 기존 함수 코드에서 { 를 앞으로 보내고 { 자리에 in을 넣어주면된다.
- enum에서 데이터 타입이나 리턴 타입이 유츄가 가능하기 때문에 파라미터나 return 값을 생략 할 수 있다.
private var operations: Dictionary<String, Operation> = [
"𝝿" : Operation.Constant(.pi), // pi
"e" : Operation.Constant(M_E), // M_E,
"√" : Operation.UnaryOperation(sqrt), // sqrt
"cos" : Operation.UnaryOperation(cos), // cos
// Operation에 정의한 ((Double, Double) -> Double)로 유추가 가능함.
//
"*" : Operation.BinaryOperation({ $0 * $1 }),
"+" : Operation.BinaryOperation({ $0 + $1 }),
"-" : Operation.BinaryOperation({ $0 - $1 }),
"/" : Operation.BinaryOperation({ $0 / $1 }),
"=" : Operation.Equals
]
// 1단계
// { 를 앞으로 보내고 in을 추가함
// "*" : Operation.BinaryOperation({(op1: Double, op2: Double) -> Double in
// return op1 * op2
// })
// 2단계
// enum에서 double 타입임을 enum에서 알 수 있기때문에 제거가가능함.
// { (op1, op2) -> Double in
// return op1 * op2
// }
// 3단계
// 인자이름으로 $0, $1이가능함
// { ($0, $1) -> Double in
// return op1 * op2
// }
// 이렇게 되면 { return $0 * $1} 로 줄일 수가 있음
// enum에서 리턴값이 double임을 알기에 {$0 * $1} 으로 줄일 수 있음
enum Operation {
case Constant(Double)
case UnaryOperation((Double) -> Double)
case BinaryOperation((Double, Double) -> Double)
case Equals
}
전체소스코드
https://github.com/HOONITANG/calculatorMVC/tree/main/calculatorMVC
후기
앱 개발을 할 때 코드 패턴을 너무 몰랐던 것 같다.
리팩토링 할 엄두가 안난다..
원래는 스토리보드에 View를 그리기 때문에 View 생성코드가 따로 없는데 Controller에서 생성코드가 있으니까 지저분해 보이긴 한다.
근데 스토리보드로 하면 비슷한 View를 만들 때 너무 힘들어서 포기했다.
'iOS' 카테고리의 다른 글
combine - 1. Combine에 관하여 (0) | 2022.03.14 |
---|---|
iOS 동시성 (GCD Grand Central Dispatch) 기본 개념 (0) | 2022.02.07 |
3. View의 생명주기 (LifeCycle) (0) | 2022.02.04 |
2. View에 관하여 (Frame, Bounds) (0) | 2022.02.04 |
Stanford iOS 한글자막 강의 - MVC 패턴 (0) | 2022.02.01 |