enum + class를 활용한 깔끔하고 확장성 높은 데이터 구조 설계
💡 어떤 앱인가요?
제가 만들고 있는 이 앱은 지출이나 수입을 이미지(카메라/앨범)와 함께 기록할 수 있는 iOS 가계부 앱입니다.
예를 들어:
- 영수증을 찍어서 저장하거나
- 감성적인 쇼핑 사진을 남기거나
- 어떤 항목에 얼마를 썼는지를 사진과 함께 기록하는 목적이에요.
그래서 금액, 카테고리, 메모 외에도 사진(image) 필드가 꼭 필요했고, 수입/지출을 명확하게 분리하면서도 관리하기 쉽도록 데이터 모델을 설계했습니다.
🧱 데이터 구조 설계
1. enum TransactionType: 수입 / 지출 타입
- 수입(income)과 지출(expense)을 구분
- 각각에 따른 카테고리 목록과 아이콘 맵핑 제공
2. class ExpenseModel: 실제 기록되는 데이터 모델
- 수입/지출 타입
- 카테고리 이름
- 금액
- 이미지 (선택)
- 날짜
- 메모
🧾 전체 코드 보기
// 수입/지출 타입을 정의하는 열거형
enum TransactionType: String {
case income
case expense
var categoryOptions: [String] {
switch self {
case .income:
return ["월급", "성과금", "용돈", "보너스", "금융소득"]
case .expense:
return [
"식비", "교통비", "문화생활", "생활품", "의류", "보험", "미용",
"의료/건강", "교육", "통신비", "회비", "세금", "경조사", "저축",
"가전", "공과금", "카드대금", "기타"
]
}
}
var categoryImageMap: [String: String] {
switch self {
case .income:
return [
"월급": "dollarsign.circle.fill",
"성과금": "gift.fill",
"용돈": "hands.sparkles.fill",
"보너스": "gift.fill",
"금융소득": "chart.line.uptrend.xyaxis"
]
case .expense:
return [
"식비": "fork.knife",
"교통비": "car.fill",
"문화생활": "music.note.house.fill",
"생활품": "cart.fill",
"의류": "tshirt.fill",
"보험": "shield.lefthalf.fill",
"미용": "scissors",
"의료/건강": "cross.case.fill",
"교육": "book.fill",
"통신비": "antenna.radiowaves.left.and.right",
"회비": "person.3.fill",
"세금": "doc.plaintext.fill",
"경조사": "gift.fill",
"저축": "banknote.fill",
"가전": "tv.fill",
"공과금": "bolt.fill",
"카드대금": "creditcard.fill",
"기타": "ellipsis.circle.fill"
]
}
}
}
// 지출/수입 항목 데이터 모델
class ExpenseModel {
let id: UUID
var transaction: TransactionType
var category: String
var amount: Int
var image: UIImage?
var date: Date
var memo: String?
init(id: UUID = UUID(),
transaction: TransactionType,
category: String,
amount: Int,
image: UIImage? = nil,
date: Date = Date(),
memo: String
) {
self.id = id
self.transaction = transaction
self.category = category
self.amount = amount
self.image = image
self.date = date
self.memo = memo
}
}
📚 모델 구성 설명
🔹 TransactionType enum
이 열거형은 가계부 항목이 수입(income) 인지 지출(expense) 인지를 명확히 구분합니다. 그리고 각 타입에 따라 선택 가능한 카테고리 목록(categoryOptions)과 시스템 이미지 이름(categoryImageMap)을 제공합니다.
장점:
- 유형에 따라 동적으로 UI 구성 가능
- 예: .income이면 "월급", .expense면 "식비" 등의 항목만 보여줌
- 카테고리별 아이콘을 SF Symbol로 매핑 가능 → 사용자에게 직관적인 UI 제공
🔹 ExpenseModel class
하나의 수입/지출 데이터를 나타내는 모델입니다. 사용자가 기록하는 모든 항목은 이 모델의 인스턴스로 관리됩니다.
| 속성 | 설명 |
| id | UUID, 고유 식별자 |
| transaction | 수입/지출 유형 (TransactionType) |
| category | 항목 카테고리 이름 |
| amount | 금액 (Int) |
| image | 첨부된 이미지 (선택사항) |
| date | 기록 날짜 |
| memo | 간단한 메모 내용 |
💡 MVVM에서 init() 초기화의 장점
위 ExpenseModel은 init(...) 생성자를 통해 즉시 속성을 초기화하도록 설계되어 있습니다. 이 방식은 MVVM 패턴과 매우 잘 어울리며, 다음과 같은 이점을 가집니다:
✅ 1. 인스턴스 생성 시점에 데이터 주입
ViewModel에서 입력값을 받아 바로 모델을 생성할 수 있습니다.
final class TransactionViewModel {
...
// MARK: - Variable
@Published var transaction: ExpenseModel?
// MARK: - Init
init(mode: AddTransactionMode? = nil) {
self.mode = mode
if let mode = mode {
switch mode {
case .create:
self.transaction = ExpenseModel(
id: UUID(),
transaction: .expense,
category: "",
amount: 0,
image: nil,
date: Date(),
memo: ""
)
case .edit(let id):
readByIDTransaction(by: id)
}
} else {
// 기본 초기화 (예: HomeViewController 등에서 사용)
self.transaction = nil
}
}
...
✅ 2. 실시간 상태 변경 가능
ViewModel에서 @Published var transaction = ExpenseModel(...) 형태로 관리하면, 사용자가 입력을 바꿀 때마다 실시간으로 모델이 업데이트되고, View와 데이터가 항상 동기화됩니다.
✅ 3. 작성과 수정에서 동일한 모델 사용
신규 작성과 기존 항목 수정 시 모두 ExpenseModel 인스턴스를 재사용하면, 코드와 UI를 통일되게 관리할 수 있습니다.
🎯 결론
이번 모델 구성은 다음과 같은 핵심 목적을 가지고 설계되었습니다:
- 사용자 경험을 고려한 카테고리 시스템
- MVVM 기반 실시간 데이터 처리에 최적화된 모델 구조
- 아이콘 매핑을 통한 직관적 UI 구현
가계부 앱의 핵심인 "빠르고 정확한 기록"을 위해 이처럼 모델 구조부터 정교하게 설계하는 것이 중요합니다.
'Project > ReceiptMind' 카테고리의 다른 글
| 💾 Core Data와 FileManager로 지출 내역 저장하기 (2) | 2025.08.01 |
|---|---|
| 📦 Swift에서 FileManager로 이미지 저장하기 – 이미지 저장, 경로 관리, 불러오기까지의 전체 흐름 정리 (1) | 2025.07.31 |
| ✅ 앱 수정 후 재심사 제출하는 방법 (App Store Connect) (3) | 2025.07.30 |
| ❌ 앱 심사 거절 사례: Guideline 5.1.1 Privacy – Purpose String 부족 (1) | 2025.07.30 |
| 📊 Swift로 주간 수입·지출 요약 만들기 (2) | 2025.07.29 |