Day 36, 37, 38 of #100DaysOfSwiftUI

Start 100DaysOfSwiftUI from 2020.Mar.18th.

Day 36, 37, 38: Project 7, part one/two/three
done with X hours

New findings: followings are new findings

sheet()
always we need to describe the condition for the behavior, this would be SwiftUI way
onDelete()
animation for delete operation looks also impressive!
modifier for NavigationStack
always need to add its child.
UserDefaults
this is what we already know, and it is almost same way to use it.
It has default value. no need to care about missing data for the key. But needs to be careful for handling it.
Codable
just adding codable works well! super! note: only in the case well-known components are used.
modifier for NavigationStack
if we need to add two items in header(leading and trailing), need to specify at same time. Later specification will overwrite previous one.

Code incl. challenges
MainView

struct ExpenseItem:Identifiable, Codable {
    let id:UUID = UUID()
    let name: String
    let type: String
    let amount: Int
}

class Expenses: ObservableObject {
    @Published var items:[ExpenseItem] {
        didSet {
            let encoder = JSONEncoder()
            if let encoded = try? encoder.encode(items) {
                UserDefaults.standard.set(encoded, forKey: "Items")
            }
        }
    }
    init() {
        if let items = UserDefaults.standard.data(forKey: "Items") {
            let decoder = JSONDecoder()
            if let decoded = try? decoder.decode([ExpenseItem].self, from: items) {
                self.items = decoded
                return
            }
        }

        self.items = []
    }
}

struct ContentView: View {
    @ObservedObject var expenses = Expenses()
    
    @State private var showingAddExpense = false
    
    var body: some View {
        NavigationView{
            List {
                ForEach(expenses.items) { item in
                    HStack {
                        VStack(alignment: .leading) {
                            Text(item.name)
                                .font(.headline)
                            Text(item.type)
                        }
                        Spacer()
                        if Int(item.amount) > 100 {
                            Text("$\(item.amount)")
                                .font(.largeTitle)
                        } else if Int(item.amount) > 50 {
                            Text("$\(item.amount)")
                               .font(.title)
                        } else {
                            Text("$\(item.amount)")
                               .font(.footnote)
                        }
                    }
                }
                .onDelete(perform: removeItems)
            }
            .navigationBarTitle("iExpense")
            .navigationBarItems(leading: EditButton(), trailing:
                Button(action: {
                    self.showingAddExpense = true
                }) {
                    Image(systemName: "plus")
                }
            )
        }
        .sheet(isPresented: $showingAddExpense) {
            AddView(expenses: self.expenses)
        }
    }
    func removeItems(at offsets: IndexSet) {
        expenses.items.remove(atOffsets: offsets)
    }
}

AddView

struct AddView: View {
    @Environment(\.presentationMode) var presentationMode

    @State private var name = ""
    @State private var type = "Personal"
    @State private var amount = ""
    
    @ObservedObject var expenses = Expenses()
    
    @State private var showingAlert = false
    @State private var alertTitle = ""
    @State private var alertBody = ""

    static let types = ["Business", "Personal"]
    
    var body: some View {
        NavigationView {
            Form {
                TextField("Name", text: $name)
                Picker("Type", selection: $type) {
                    ForEach(Self.types, id:\.self) {
                        Text($0)
                    }
                }
                TextField("Amount", text: $amount, onCommit: {
                })
                    .keyboardType(.numberPad)
            }
        .navigationBarTitle("Add new expense")
            .navigationBarItems(trailing: Button("Save") {
                if let actualAmount = Int(self.amount) {
                    let item = ExpenseItem(name: self.name, type: self.type, amount: actualAmount)
                    self.expenses.items.append(item)
                    self.presentationMode.wrappedValue.dismiss()
                } else {
                    self.alertTitle = "invalid amount"
                    self.alertBody = "Amount can not be converted to number"
                    self.showingAlert = true
                }
            })
                .alert(isPresented: $showingAlert) {
                    Alert(title: Text(self.alertTitle),
                          message: Text(self.alertBody),
                          dismissButton: .default(Text("OK")))
            }
        }
    }
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です