혼자만의 미니 프로젝트

UIKit | 계산기 만들기

ziziDev 2024. 6. 6. 23:29
반응형


플레이그라운드로 단순 계산기 만들기를 제출한 후

UIKit로 만들면 재미있을것 같은데..라는 생각이 들어서 후다닥 만들어 보았습니다

스토리보드와 익숙해 지길 바라며 스토리보드와 코드를 연동하여 진행하였습니다

 

우선 가로줄 한줄 (4개의 버튼 / 마지막 3개의 버튼)이 스택뷰로 묶여있습니다

그리고 전체 버튼으로도 스택뷰로 이루어져 있습니다

라벨은 스택뷰 위에 30정도 위에 떨어져있습니다

 

다음으로 스토리보드와 코드 연결 및 기능을 구현해 보았습니다

 

그리고 0같은경우 정렬의 기능을 사용하지않고

 

Title Insets를 사용해서 

공간삽입을 진행하였습니다

 

 

 @IBOutlet var buttons: [UIButton]!

 

로 선언한 후 

동그란 원 버튼을 만들기위해서 열심히 구글링을 했습니다

 

그 결과

https://nareunhagae.tistory.com/38

 

[cornerRadius] - 동그란 버튼을 만들고 싶을 때

원 모양 만들고 싶은데.. 외않되...★ 하는 당신. 잘 찾아 오셨습니다. 원 모양 버튼을 만들기 전에 cornerRadius를 가볍게 알아봅시다! button 속성에 접근할 때 " 내가만든버튼이름.layer.cornerRadius " 로

nareunhagae.tistory.com

 

을 읽고 진행해 보았습니다

 

 

 

 

 private func updateButtonUI() {
        
        guard let buttons = buttons else { return }
        
        buttons.forEach { button in
            button.layer.cornerRadius = button.layer.frame.size.width / 2
        }
        
    }

 

직 사각형도 이쁘게 나올줄 알았지만

착각이였습니다

눈 왜 네모나게 떠 세모나게떠???? 도 아닌..

웬 고양이가 절 쳐다보고 있기에 다시 숙지하고 코드도 변경하였습니다

 

 

마지막 0버튼은 연결해지를 하고

zeroButton 변수를 하나 추가한 후

너비의 1/4 로 적용해야 제가 원하던 아이폰 계산기 앱의 둥근 네모가 나올것 같아

적용해보았습니다

 

zeroButton.layer.cornerRadius = zeroButton.layer.frame.size.width / 4

 

 

아직 좌우 끝 부분이 조금 뾰족하기에 좀 더 수치를 줄여서 사용하기로 했습니다

5를 넣었지만 이쁘지 않아서 다시 고민이 가득한저... 

4.5가 적당하다는 말인데

 소수점 넣긴 싫었지만 넣어줬습니다 미적 아름다움을 위해서 ^^

zeroButton.layer.cornerRadius = zeroButton.layer.frame.size.width / 4.5

 

그리고 다음으로 버튼이 눌리면 어떠한 액션이 취해져야하니 버튼 함수를 만들어줍니다

우선 연산자들을 다 나눠서 액션 메서드를 만들고 싶었지만

코드로 처리하고 싶기에 그냥 하나에 다 밀어넣었습니다 ^^

 

구조를 생각했을때

델리게이트를 사용해서

각연산(operator + number + clear + allclear + equal)마다 각각 나눠서 관리할지

 

아님 이넘(operator)과 프로토콜(연산 메서드)을 만들어서 각연산을 클래스로 만들어서

계산 매니저를 만들어서 관리할지

 

마지막으로 연산 기호를 enum으로 설정하고 거기서 사칙연한 함수를 하나 넣고 operator / numtapped두 개의 메서드를 담고 있는 프로토콜을 만들어 계산 매니저안에 넣어서 관리할 지 고민했습니다

 

사실 그냥 버튼 하나하나 그룹별로 IBAction으로 만들어서 사용하면 더 빨리 끝나겠지만

그냥 저렇게 관리하면 좋겠다라고 생각했습니다

 

그래서 순서대로 코드를 짜보았습니다

 

1

은 스토리보드로 연결하여서 각각 기능에 맞게 구현하였습니다

그래서 별도의 코드는 첨부하지 않겠습니다

 

 

2

class CalculationManager {
    weak var delegate: CalculatorButtonDelegate?
    
    func buttonTapped(type: String) {
        if Int(type) != nil {
            delegate?.numberButtonTapped(number: type)
        } else {
            switch type {
            case "+", "-", "×", "÷":
                delegate?.operatiorButtonTapped(operatorType: type)
            case "C":
                delegate?.clearButtonTapped()
            case "AC":
                delegate?.allClearButtonTapped()
            case "=":
                delegate?.equalButtonTapped()
            case "%":
                delegate?.percentageButtonTapped()
            case "+/-":
                delegate?.invertButtonTapped()
            case ".":
                delegate?.dotButtonTapped()
            default:
                break
            }
        }
    }
}

 

protocol CalculatorButtonDelegate: AnyObject {
    func operatiorButtonTapped(operatorType: String)
    func numberButtonTapped(number: String)
    func clearButtonTapped()
    func allClearButtonTapped()
    func equalButtonTapped()
    func percentageButtonTapped()
    func invertButtonTapped()
    func dotButtonTapped()
}

 

protocol CalOperation {
    func performOperation(first: Double, second: Double) -> Double?
}

class AdditionOperation: CalOperation {
    func performOperation(first: Double, second: Double) -> Double? {
        return first + second
    }
}

class SubtractionOperation: CalOperation {
    func performOperation(first: Double, second: Double) -> Double? {
        return first - second
    }
}

class MultiplicationOperation: CalOperation {
    func performOperation(first: Double, second: Double) -> Double? {
        return first * second
    }
}

class DivisionOperation: CalOperation {
    func performOperation(first: Double, second: Double) -> Double? {
        guard second != 0 else { return nil }
        return first / second
    }
}

 

나머지 뷰컨트롤러에 델리게이트를 추가하여

나머지 작업을 완료하였습니다

 

 

3

protocol CalculatorButtonDelegate: AnyObject {
    func didTappedOperator(_ op: CalOperator)
    func didTappedNumber(_ number: String)
}

 

class CalculationManager {
    weak var delegate: CalculatorButtonDelegate?
    
    func buttonTapped(title: String) {
        if let number = Double(title){
            delegate?.didTappedNumber(title)
        } else if let op = CalOperator(rawValue: title) {
            delegate?.didTappedOperator(op)
        }
    }
}
enum CalOperator: String {
    case clear = "C"
    case allClear = "AC"
    case invert = "+/-"
    case percentage = "%"
    case dot = "."
    case addition = "+"
    case subtraction = "-"
    case multiplication = "×"
    case division = "÷"
    case equals = "="
    case none = "0"
    
    func performOperation(first: Double, second: Double) -> Double? {
        switch self {
        case .addition:
            return first + second
        case .subtraction:
            return first - second
        case .multiplication:
            return first * second
        case .division:
            return second != 0 ? first / second : nil
        default:
            return nil
        }
    }
}

 

 @IBAction func buttonTapped(_ sender: UIButton) {
        guard let title = sender.currentTitle else { return }
        buttonManager.buttonTapped(title: title)
    }
    
    func didTappedOperator(_ operation: CalOperator) {
        switch operation {
        case .clear:
            currentNumber = 0
            displayValue = "0"
            clearButton.setTitle("AC", for: .normal)
        case .allClear:
            oldNumber = 0
            currentNumber = 0
            displayValue = "0"
        case.dot:
            displayValue += "."
            currentNumber = Double(displayValue) ?? 0
        case .invert:
            currentNumber = Double(displayValue) ?? 0
            currentNumber = -currentNumber
            displayValue = "\(currentNumber)"
        case .percentage:
            currentNumber = Double(displayValue) ?? 0
            currentNumber = currentNumber * 0.01
            displayValue = "\(currentNumber)"
        default:
            if let currentOp = self.op  {
                currentNumber = Double(displayValue) ?? 0
                if let result = currentOp.performOperation(first: oldNumber, second: currentNumber) {
                    displayValue = "\(result)"
                    oldNumber = result
                }
            } else {
                oldNumber = Double(displayValue) ?? 0
            }
            
            //연산자 설정
            op = operation == .equals ? nil : operation
            break
        }
        
        updateCalculationUI()
       }
       
    func didTappedNumber(_ number: String) {
        
        if op != nil {clearButton.setTitle("C", for: .normal)}
        
        if op != nil && displayValue == "\(oldNumber)" {
            // 현재 연산자가 있고 디스플레이 값이 이전 숫자와 같을 때
            displayValue = number
        } else {
            if displayValue == "0" && number != "." {
                displayValue = number
            } else {
                if number == "." && displayValue.contains(".") {
                    return
                }
                displayValue += number
            }
        }
        updateCalculationUI()
    }
       
    func updateCalculationUI() {
        calculationLabel.text = displayValue
    }

 

마지막 방법같은 경우는 enum내에 함수를 넣고 사칙연산을 할

경우가 되면 메서드를 호출하는 형식으로 만들어 보았습니다

 

이렇게 3가지 방법으로 한 결과 1,2번 방법이 제일 효율적으로 짤 수 있었습니다

기능이 추가된다고해도 델리게이트에 메서드를 추가하는 방법을 모색할 수 있습니다

 

3같은 경우 뷰컨트롤러 내부에서 많이 작성하다 보니 유지보수 부분에서 힘들것 같다라는 생각도 들었습니다

 

제가 뷰컨트롤러에서 생각한 방법은 연산자를 누르기전 값을 저장하는 변수를 하나 만들고(oldValue)

 

연산자를 하나 담는 변수(Caloperator)

 

그리고 연산자를 누른 후 입력한 숫자를 저장하는 변수가 필요하다고 생각했어요(currentValue)

그리고 마지막으로 oldValue와 currentValue의 연산이 끝난 resultValue 총 3가지가 필요하다고 판단했습니다

 

그리고 사칙연산자를 누른적이 있는지 유무를 판단 후 있다면 currentValue

사칙연산을 누른적이 없다면 oldValue에 넣고

 

연산을 하게끔 작성했습니다

 

반복적으로 = 대신 사칙연산 숫자를 반복적으로 누를 때마다 oldvalue로 연산이 되더라구요

그래서 =이 아닌 사칙연산을 다시 사용할 시 currentValue로 연산하는게 아닌 oldvalue를 이용하여 값을 도출하는 형식으로 사용하였습니다

 

 

 

현재 Double형이기 때문에 소수점이 나오지만

마지막 연산을 도출할 때 1로 나누어지는지 유무를 확인후 정수형으로 변경하는 것도 추가해서 보완할 예정입니다

 

보완

 

 

연산하는 곳에서 결과값을 받아서 updateCalculationUI()로 가기전에 1로 나눴을 때 나머지가 0 이라면

Int형으로 자료변환하여 적용하였습니다

반응형