본문 바로가기
IT/swift

100 days of SwiftUI - Day37

by 가능성1g 2024. 12. 8.
반응형

이번 프로젝트는 가계부 프로젝트!

이름은 iExpense 애플 스러운 이름이다. 

소스를 보자. 2개의 파일이 추가되었다.

//
//  ContentView.swift
//  iExpense
//
//  Created by HanTJ on 12/1/24.
//

import SwiftUI

struct ExpenseItem: Identifiable, Codable { //저장을 위한 Codable
    var id = UUID()  //UUID자동생성!
    let name: String
    let type: String
    let amount: Double
}

@Observable
class Expenses {
    var items = [ExpenseItem]() {
        didSet {
            // persistent 저장!
            if let encoded = try? JSONEncoder().encode(items) {
                UserDefaults.standard.set(encoded, forKey: "Items")
            }
        }
    }
    
    //처음 클래스 생성시 persistent 를 복원
    init() {
        if let savedItems = UserDefaults.standard.data(forKey: "Items") {
            if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from:savedItems) {  //self는 꼭 써야하는건가..
                items = decodedItems
                return
            }
        }
        items = [] //에러방지! 오류나면 빈값으로 초기화
    }
}

struct ContentView: View {
    @State private var expenses = Expenses()
    @State private var showingAddExpense = false
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(expenses.items) { item in  //id항목 않써줘도 된다 why? Identifiable 프로토콜을 구현했기 때문에!
                    HStack {
                        VStack {
                            Text(item.name)
                                .font(.headline)
                            Text(item.type)
                        }
                        Spacer()
                        Text(item.amount, format: .currency(code: "USD"))
                    }
                }
                .onDelete(perform: removeItems)
            }
            .navigationTitle("iExpense")
            .toolbar {
                Button("Add Expense", systemImage: "plus") {
                    showingAddExpense = true
                }
            }
            .sheet(isPresented: $showingAddExpense) {
                AddView(expenses: expenses)
            }
        }
 
    }
    
    //delete item의 공식같이 익혀두자.
    func removeItems(at offsets: IndexSet) {
        expenses.items.remove(atOffsets: offsets)
    }
}

#Preview {
    ContentView()
}

클래스와 구조체를 이용해서 기본 저장 구조를 만들었다. 

클래스에 Observable 을 쓰고 , 클래스 초기화와 내부 변수의 할당후 로직으로 저장을 만들었다. 위치는 UserDefaults 영역

그리고 드뎌! 새로운 뷰를 호출하는 방법을 했다. sheet  modifier 를 이용했고, showingAddExpense 라는 바인드 변수로 보이고 안보이고를 선언했다. 그리고 뷰를 생성하면 ㅇㅋ

 

추가된 화면인 AddView 이다.

//
//  AddView.swift
//  iExpense
//
//  Created by HanTJ on 12/4/24.
//

import SwiftUI

struct AddView: View {
    @State private var name = ""
    @State private var type = "Personal"
    @State private var amount = 0.0
    @Environment(\.dismiss) var dismiss
    
    let types = ["Business", "Personal"]
    
    var expenses: Expenses
    
    var body: some View {
        NavigationStack {
            Form {
                TextField("Name", text: $name)
                
                Picker("Type", selection: $type) {
                    ForEach(types, id: \.self) {
                        Text($0)
                    }
                }
                
                TextField("Amount", value: $amount, format: .currency(code: "USD"))
                    .keyboardType(.decimalPad)
            }
            .navigationTitle("Add new expense") //. 빼먹었떠니 컴파일 오류는 아닌데 실행이 안되더라.. 오호..
            .toolbar {
                Button("Save") {
                    let item = ExpenseItem(name: name, type: type, amount: amount)
                    expenses.items.append(item) //신규로 만들걸 저장한다. 메인 ContnetView에 있는 expenses에 하는것!
                    dismiss()
                }
            }
        }
    }
}

#Preview {
    AddView(expenses: Expenses())
}

그동안 공부했던거 + 추가 뷰를 닫기 위한 dismiss 사용법이다. 데이터를 공유하기 위해 생성시 expenses 클래스를 참조변수로 받았다.

그리고 Preview도 생성 인수가 필요하니, 빈값으로 Expenses 클래스 생성을 추가 했다!

 

반복하니 먼가 슬슬 익숙하다!

반응형