[TIL] iOS 입문 주차 과제 : 계산기 앱 만들기 day.01

Emily·2024년 11월 11일
2

CalculatorApp

목록 보기
6/11
post-thumbnail

(먼저 만들어 본 거 뿌듯해서 만족한 상태)

새로운 주차가 시작되었다. 지금까지는 프로그래밍 언어인 Swift의 문법을 학습했다면, 오늘부터는 iOS 앱 개발을 학습한다. 문법 기초 주차 때 과제가 playground를 통해 사칙연산 기능을 구현하는 것이었는데, 그 연산 기능을 수행하는 계산기 앱을 UIKit을 통해 구현하는 것이 이번 과제로 주어졌다. 나는 문법 주차 때 과제 외 개인적인 도전으로 앱 프로젝트를 같이 시작했었는데, 그게 이렇게 돌아올 줄은 몰랐다. 이미 만들어놓은 프로젝트의 틀을 이번 과제로 옮겨올 수 있게 됐다. 다만 과제에서 제시한 구현 사항을 지키느라 레이아웃의 대부분을 수정했다.

계산기 앱 구현 기본사항

[UI]

  • 컴포넌트 : UILabel, UIButton, UIStackView 사용
  • AutoLayout 적용
  • UIButton 동작 방식 : addTarget
  • UIKit으로 구현(storyboard 또는 code base 선택, 둘 다 하면 좋음)

[Logic]

  • Int 계산만 구현
  • = 버튼을 눌렀을 때만 연산이 이루어지도록 구현
  • 연산은 Swift의 기본 제공 메소드 활용

필수 구현기능 Lv.1 ~ 5

01) UILabel

숫자 버튼과 연산자 버튼을 눌렀을 때 입력 내용이 띄워지는 텍스트 구간이다.

// main view controller에 컴포넌트 선언
private lazy var inputLabel: UILabel = {
	let label = UILabel()
    
    label.backgroundColor = .black
	label.textColor = .white
    label.textAlignment = .right
	label.font = .systemFont(ofSize: 60, weight: .bold)

    return label
}()

// 레이아웃 지정 함수
private func layout() {
	let superView = view.safeAreaLayoutGuide
        
    let offset: CGFloat = 30.0
        
    NSLayoutConstraint.activate([
        inputLabel.leadingAnchor.constraint(equalTo: superView.leadingAnchor, constant: offset),
        inputLabel.trailingAnchor.constraint(equalTo: superView.trailingAnchor, constant: -offset),
        inputLabel.topAnchor.constraint(equalTo: superView.topAnchor, constant: 200),
        inputLabel.heightAnchor.constraint(equalToConstant: 100),
    ])
}

02) UIButton

버튼 정보를 갖고 있는 Model을 정의했다.

struct ButtonInfo {
	let role: ButtonRole
    let name: ButtonName
}

// butto role은 숫자와 숫자가 아닌 버튼의 구분, button name은 각 버튼의 오리지널리티를 나타낸다. 내용 코드 생략.
enum ButtonRole {}
enum ButtonName {}

UIButton을 상속하는 커스텀 클래스를 정의했다. ButtonInfo를 이니셜라이징 하도록 하여 buttonInfo.role, buttonInfo.name에 따라 버튼 color, title, image가 지정되도록 구현했다.

class Button: UIButton {
    let buttonInfo: ButtonInfo
    
    init(buttonInfo: ButtonInfo) {
        self.buttonInfo = buttonInfo
        super.init(frame: .zero)
        layout()
    }
    
    private func layout() {
		// 과제에서 지정한 button layout 지정 //
    }
}

[!] 과제에서는 곱하기, 나누기 버튼 모양이 *, /지만 나는 ×, ÷ 모양을 넣고 싶기 때문에 SF Symbols를 활용하기로 했다. UIImage(systemName: "")에 심볼 이름을 넣으면 애플에서 제공하는 이미지를 버튼에 넣을 수 있다.

private func setButton() {
	switch buttonInfo.name.withImage {
    case true:
        setImage(buttonInfo.name.systemName)
    case false:
        setTitle(buttonInfo.name.title)
    }
}

ButtonName enumwithImage라는 Bool 타입 연산 프로퍼티를 정의하여 이미지 넣을 버튼을 구분하도록 구현했다.

03) UIStackView

4개의 버튼이 수평으로 들어가는 horizontalStackView와, 그 horizontalStackView가 수직으로 4개 들어가는 verticalStackView를 만들었다. 그렇게 총 16개의 버튼이 들어가는 것이다.

Horizontal

class HorizontalStackView: UIStackView {
    init() {
        super.init(frame: .zero)
        layout()
    }
    
    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func layout() {
        axis = .horizontal
        spacing = 10
        distribution = .fillEqually
        
        translatesAutoresizingMaskIntoConstraints = false
        heightAnchor.constraint(equalToConstant: 80).isActive = true
    }
    
    // 외부(verticalStackView)에서 호출하여 button을 연속적으로 sub view에 추가하는 함수
    func addSubviews(_ buttons: [Button]) {
        buttons.forEach {
            addArrangedSubview($0)
        }
    }
}

Vertical

class VerticalStackView: UIStackView {
    
    // horizontal stack view를 4개 정의하였다.
    private lazy var firstHStack: HorizontalStackView = {
        let stackView = HorizontalStackView()
        
        let sevenButton = Button(buttonInfo: .init(role: .number, name: .seven))
        let eightButton = Button(buttonInfo: .init(role: .number, name: .eight))
        let nineButton = Button(buttonInfo: .init(role: .number, name: .nine))
        let addButton = Button(buttonInfo: .init(role: .operation, name: .add))
        
        stackView.addSubviews([sevenButton, eightButton, nineButton, addButton])
        
        return stackView
    }()
    
    // ... //
    
    // 정의한 4개의 hstack을 sub view로 추가
    private func addSubviews() {
        [firstHStack, secondHStack, thirdHStack, fourthHStack]
            .forEach {
                addArrangedSubview($0)
            }
    }
}
// main view controller에서 vstack을 컴포넌트로 선언
private lazy var buttonView = VerticalStackView()

04) UIColor extension

과제 요구사항에서 지정해준 숫자 버튼과 연산자 버튼의 색깔을 UIColorextension하여 타입 프로퍼티로 정의하였다.

extension UIColor {
    static let numbersButtonColor = UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0)
    static let operatorButtonColor = UIColor.systemOrange
}

이렇게 해놓으면, Button 클래스에서 role에 따라 backgroundColor를 지정할 때 내가 정의한 이름으로 넣으면 된다.

private func setColor() {
	switch buttonInfo.role {
    case .number:
        backgroundColor = .numbersButtonColor
     case .operation:
        backgroundColor = .operatorButtonColor
    }
}

05) 완성한 뷰

내 프로젝트와 많이 다르다. 버튼이 더 적기도 하고, 나는 StackView를 적용하지 않았었는데 이번 기회에 적용해보게 됐다. 그냥 UIView 안에 버튼 여러개를 때려넣는 것보다 코드가 훨씬 줄었다.

profile
iOS Junior Developer

0개의 댓글