100 Days of SwiftUI Day 37

100 Days of SwiftUI – Day 37

It’s day 37 of the 100 Days of SwiftUI! Yesterday, we’ve had our introduction to the iExpense app, our 7th project. Today, we make the app and polish it up so that’s fully functional and usable.

This is the first project were we’ve been working in various files, instead of only the ContentView, so I’ll be breaking up my code accordingly so that it’s easier to read and understand. I’ve added tons of comments in the code explaining what’s happening.

ExpenseItem

We’ve created a new file called ExpenseItem.swift. This file contains a struct for an ExpenseItem.

import Foundation

struct ExpenseItem: Identifiable, Codable { // Represents a single expense, Identifiable means that it can be identified, codable allows the JSON Encoder/Decoder to work with the data in the struct
    var id = UUID() // Generates a unique ID for each instance of ExpenseItem
    let name: String
    let type: String
    let amount: Double
}

Expenses

The second file we created is Expenses.swift and it contains our Expenses class.

import Foundation

class Expenses: ObservableObject {
    @Published var items = [ExpenseItem]() { // An array of ExpenseItem
        didSet { // the code within didSet will run any time a change is detected in our ExpenseItem array
            let encoder = JSONEncoder() // create an instance of the JSONEncoder
            
            if let encoded = try? encoder.encode(items) { // create an encoded variable that will store the encoded items. Try? is used cause it might throw an error
                UserDefaults.standard.set(encoded, forKey: "Items") // Store our encoded items with the key "Items"
            }
        }
    }
    
    init() {
        if let savedItems = UserDefaults.standard.data(forKey: "Items") { // read data from UserDefaults for the key "Items"
            if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) { // If we can read the data, attempt decoded and convert the data into ExpenseItem.self. .self in this case means that mean an array of ExpenseItems.
                items = decodedItems // the array of ExpenseItem is now an array of ExpenseItem that contains the decodedItems
                return
            }
        }
        
        items = [] // If code does not run, for example if the data can't be read or there isn't any data to be read, return an empty array instead

    }
}

AddView

In order to add new items to our items array in a clean and user-friendly way, we created a new view where the user can fill out the details for their expense.

import SwiftUI

struct AddView: View {
    @ObservedObject var expenses: Expenses // Tells SwiftUI that our view requires an instant of the Expenses class to be passed in to read/modify. We are NOT creating a new instance, we are just passing one in that already exists.
    @Environment(\.dismiss) var dismiss // dismisses the view when it is called
    
    @State private var name = ""
    @State private var type = "Personal"
    @State private var amount = 0.0

    let types = ["Business", "Personal"]
    
    var body: some View {
        NavigationView {
            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) // Used to get the number specific keyboard
            }
            
            .navigationTitle("Add new expense")
            .toolbar{
                Button("Save") {
                    let item = ExpenseItem(name: name, type: type, amount: amount)
                    expenses.items.append(item) // We create an item and bind the values of our form to the item, then add that item to our list of items in the instance of the expenses class.
                    dismiss() // closes the view when the save button is pressed
                }
            }
        }
    }
}

struct AddView_Previews: PreviewProvider {
    static var previews: some View {
        AddView(expenses: Expenses())
    }
}

ContentView

Lastly, our ContentView, the main view for our app.

import SwiftUI



struct ContentView: View {
    
    @StateObject var expenses = Expenses() // create a new instance of our Expenses class
    @State private var showingAddExpense = false // Used to track whether our sheet containing our AddView view should be sown or not
    
    var body: some View {
        NavigationView {
            List {
                ForEach(expenses.items) { item in // Loop through our items in the expenses class instance
                    HStack {
                        VStack(alignment: .leading) {
                            Text(item.name)
                                .font(.headline)
                            Text(item.type)
                        }
                        
                        Spacer()
                        
                        Text(item.amount, format: .currency(code: "USD"))
                    }
                }
                .onDelete(perform: removeItems) // Used to delete a row
            }
            .navigationTitle("iExpense")
            .toolbar{
                Button {
                    showingAddExpense = true // If the button is pressed, a sheet is presented with our AddView view
                } label: {
                    Image(systemName: "plus")
                }
            }
            
            .sheet(isPresented: $showingAddExpense) { // presents a sheet when showingAddExpense = true
                AddView(expenses: expenses) // shows our AddView view and passes in our instance of the expenses class
                
            }
        }
    }
    
    func removeItems(at offsets: IndexSet) { // function used to delete items in our list
        expenses.items.remove(atOffsets: offsets)
    }
    
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

iExpense SwiftUI Wrap up

And that’s it for day 37! I hope you enjoyed going through the development of this app as much as I did. Tomorrow, we’ll review what we’ve learned in this project as well as face a few challenges. Time to recharge the batteries!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 23 -Views and Modifiers

100 Days of SwiftUI – Day 66

100 Days of SwiftUI – Day 52

100 Days of SwiftUI – Day 20 – Guess the Flag