TIL: Kiosk Application 구현 - ViewController와 ViewModel (Kiosk)

버질·2024년 11월 26일

이번 프로젝트에서는 iOS 애플리케이션의 키오스크 시스템을 구현했습니다.
구조적으로 MVVM 패턴을 활용하여 ViewController와 ViewModel을 분리해 데이터 흐름과 로직 관리를 명확히 했습니다.

구현 내용

1. ViewController (UI 구성 및 이벤트 처리)

앱의 메인 화면을 관리하며, UI 컴포넌트를 배치하고 사용자 이벤트를 처리.
뷰 계층을 구성하고 SnapKit으로 제약 조건을 설정.
Kiosk ViewModel과의 데이터 바인딩을 통해 UI와 데이터를 연동.
주요 구현

class ViewController: UIViewController {
    private let headerView = HeaderView()
    private let menuView = MenuView()
    private let cartView = CartView()
    private let buttonView = ButtonsView()
    private let kiosk = Kiosk() // ViewModel

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupBindings()
        kiosk.getCurrentMenuItems() // 초기 데이터 로드
    }
}

UI 설정 (setupUI)
SnapKit을 활용하여 헤더, 메뉴, 장바구니, 버튼 영역의 레이아웃을 설정.
각 컴포넌트의 높이 비율을 적절히 조정하여 사용자 경험을 극대화.

headerView.snp.makeConstraints { make in
    make.top.equalTo(view.safeAreaLayoutGuide)
    make.leading.trailing.equalToSuperview()
    make.height.equalTo(100)
}
menuView.snp.makeConstraints { make in
    make.top.equalTo(headerView.snp.bottom)
    make.leading.trailing.equalToSuperview()
    make.height.equalTo(view.frame.height * 0.4)
}

바인딩 (setupBindings)
각 UI 컴포넌트의 동작을 ViewModel에 연결.
사용자 입력 이벤트(버튼 클릭, 카테고리 선택 등)를 처리하여 데이터 상태를 갱신.
데이터 변경 시, ViewModel의 클로저를 통해 UI를 업데이트.

headerView.onCategorySelected = { [weak self] index in
    self?.kiosk.selectCategory(at: index)
}

kiosk.onMenuUpdated = { [weak self] menuItems in
    self?.menuView.updateMenuItems(menuItems)
}

Alert 메시지 표시
주문 완료 또는 직원 호출 등의 메시지를 사용자에게 표시.

private func showAlert(title: String, message: String) {
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
    present(alert, animated: true, completion: nil)
}

2. Kiosk (ViewModel - 데이터와 로직 관리)

데이터 관리 및 비즈니스 로직 처리.
카테고리 변경, 장바구니 관리, 주문 완료 등의 로직을 처리.
클로저를 활용해 데이터 변경 사항을 ViewController에 전달.
주요 구현
카테고리별 데이터 관리
더미 데이터를 활용하여 카테고리를 나눠 구성.
currentCategoryIndex를 통해 선택된 카테고리를 관리하고, 해당 데이터를 반환.

private let menuCategories: [[MenuItem]] = [
    [MenuItem(name: "📱 스마트폰", price: 1000)],
    [MenuItem(name: "🔨 망치", price: 1200)],
    [MenuItem(name: "❤️ 심장", price: 4000)]
]

func getCurrentMenuItems() {
    let currentMenu = menuCategories[currentCategoryIndex]
    onMenuUpdated?(currentMenu)
}

장바구니 관리
아이템 추가, 수량 변경, 삭제 등의 로직 처리.
아이템이 중복될 경우 수량 증가, 수량이 0이 되면 제거.

func addItemToCart(menuItem: MenuItem) {
    if let index = cartItems.firstIndex(where: { $0.name == menuItem.name }) {
        cartItems[index].quantity += 1
    } else {
        cartItems.append(CartItem(name: menuItem.name, price: menuItem.price, quantity: 1))
    }
    updateCart()
}

주문 완료 및 초기화
주문 완료 시 총 금액과 상품 수량을 계산 후 장바구니 초기화.

func completeOrder() -> String {
    let totalItems = cartItems.reduce(0) { $0 + $1.quantity }
    let totalPrice = cartItems.reduce(0) { $0 + ($1.price * $1.quantity) }
    clearCart()
    return "총 \(totalItems)개의 상품 | 총 금액: \(totalPrice)원\n주문이 완료되었습니다."
}

클래스 구조
MenuItem과 CartItem
MenuItem: 메뉴 아이템 정보 (이름, 가격).
CartItem: 장바구니 내 아이템 정보 (이름, 가격, 수량).

struct MenuItem {
    let name: String
    let price: Int
}

struct CartItem {
    let name: String
    let price: Int
    var quantity: Int
}

Kiosk (ViewModel)
메뉴 데이터 관리.
장바구니 로직 처리.
클로저를 활용한 View와의 데이터 연동

학습 포인트

MVVM 패턴의 이해
ViewController는 UI와 이벤트 처리에만 집중하고, 비즈니스 로직은 ViewModel로 분리.
클로저를 사용해 데이터와 UI를 동기화.

SnapKit을 활용한 레이아웃 관리
코드를 간결하게 유지하며, 직관적인 레이아웃 설정 가능.

데이터와 로직의 분리
Kiosk를 통해 데이터와 비즈니스 로직을 중앙에서 관리.
코드의 재사용성과 유지보수성을 크게 향상.

사용자 경험 (UX) 고려
UI 구성 요소를 적절히 배치하여 사용자 중심의 인터페이스 설계.

느낀 점

MVVM 패턴을 실제 프로젝트에 적용하면서 View와 데이터 로직 간의 역할 분리가 얼마나 중요한지 느꼈습니다.
SnapKit을 활용해 레이아웃 설정을 보다 직관적으로 작성할 수 있었고, 클로저 기반 데이터 연동 방식의 편리함을 경험했습니다.
이번 경험을 바탕으로 더 복잡한 앱에서도 MVVM 패턴과 클로저를 활용해 효율적인 구조를 설계하고 싶습니다.

profile
iOS Developer · SwiftUI & UIKit '가끔 되고 가끔 안 되는' 문제를 뿌리부터 잡습니다.

0개의 댓글