简体   繁体   中英

SwiftUI. How do I access/manipulate data between views

This code is not specifically mine but it will show the problem I am having more clearly. It is a part of an expense calculation application. I am having a hard time manipulating data after transferring it between views. I have 2 views and I would like to take all of the expenses and add them up. Part of the problem is that I make a new instance of a class every time I add a new "expense".

import SwiftUI

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

    @State private var name = ""
    @State private var type = "Personal"
    @State private var amount = ""
    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)
                    .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()
                }
            })
        }
    }
}

//SecondView

import SwiftUI

struct ExpenseItem: Identifiable, Codable {

let id = 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
            }
        }
    }
}

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 {
                            Text(item.name)
                                .font(.headline)
                            Text(item.type)
                        }

                        Spacer()
                        Text("$\(item.amount)")
                    }
                }
                .onDelete(perform: removeItems)
            }
            .navigationBarTitle("iExpense")
            .navigationBarItems(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)
    }
}

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

It really depends on where you want to show the total. The easiest solution is to display the total on the ContentView , after the list of all the expenses.

But first we need to calculate the total. As the total relates to the our Expenses ObservableObject it makes sense that it calculates it is own total.

The Expenses class only has one instance and it contains multiple instances of the ExpenseItem struct. We are going to solve this by adding a computed property to the Expenses class. I have removed some code from your examples so as to highlight the changes that I have made.

class Expenses: ObservableObject {
    @Published var items: [ExpenseItem]

    // ...

    // Computed property that calculates the total amount
    var total: Int {
        self.items.reduce(0) { result, item -> Int in
            result + item.amount
        }
    }
}

The computed property total reduces all the ExpenseItem amounts into a single value. You can read more about reduce here , and you can read about computed properties here . We don't have to use a reduce, we could use a for-loop and sum the values. We don't have to use a computed property either, we could use a function. It really depends on what you want.

Next in the ContentView we can add a view to the List that shows the total.

struct ContentView: View {
    @ObservedObject var expenses = Expenses()
    @State private var showingAddExpense = false

    var body: some View {
        NavigationView {
            List {
                ForEach(expenses.items) { item in
                   // ...
                }
                .onDelete(perform: removeItems)

                // View that shows the total amount of the expenses
                HStack {
                    Text("Total")
                    Spacer()
                    Text("\(expenses.total)")
                }
            }
            // ...
    }
     // ...
}

This will give you a result that looks like this.

显示 iExpense 应用程序总计的图像

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM