Home > OS >  How do I create a dynamic input list?
How do I create a dynamic input list?

Time:08-17

I have some accounts that I want to get a user to input a number to provide an initial balance.

Not sure how to do the binding. Here's my model:

class DefaultBalancesViewModel: ObservableObject {
    @Published var accountBalances: [Account: String] = [:]
    @Published var accounts: [Account] = []

    private let dbCore: DBCore

    init(dbCore: DBCore = DBCore.shared) {
        self.dbCore = dbCore
        self.accounts = dbCore.accounts
        self.accountBalances = dbCore.accounts.reduce(into: [Account: String]()) { $0[$1] = "" }
    }
}

and my basic SwiftUI:

struct DefaultBalancesView: View {
    @StateObject var viewModel = DefaultBalancesViewModel()

    var body: some View {
        ForEach(viewModel.accounts) { account in
            HStack {
                Text(account.name)
                Spacer()
                TextField("", text: $viewModel.accountBalances[account]) // <-- error here: Cannot convert value of type 'Binding<[Account : String]>.SubSequence' (aka 'Slice<Binding<Dictionary<Account, String>>>') to expected argument type 'Binding<String>' (and a couple of other errors)
            }
        }
        Button("Save") {
            //
        }
    }
}

How should I structure this so I can dynamically enter a number for each account?

CodePudding user response:

At the moment you have multiple copies of Account arrays, and a dictionary with String! There should be only one source of truth, eg @Published var accounts: [Account], and the Account should be able to hold (or calculate) its balance.

Try this approach (works well for me), where you have only one array of [Account] (one source of truth), that you can edit using a TextField:

Customise the code to suit your needs, e.g the currency, or simply a "normal" decimal formatter.

class DefaultBalancesViewModel: ObservableObject {
    // for testing
    @Published var accounts: [Account] = [Account(name: "account-1", balance: 1.1),
                                        Account(name: "account-2", balance: 2.2),
                                        Account(name: "account-3", balance: 3.3)]
    
    private let dbCore: DBCore

    init(dbCore: DBCore = DBCore.shared) {
        self.dbCore = dbCore
        self.accounts = dbCore.accounts
    }
}

struct DefaultBalancesView: View {
    @StateObject var viewModel = DefaultBalancesViewModel()

    var body: some View {
        ForEach($viewModel.accounts) { $account in  // <-- here
            HStack {
                Text(account.name).foregroundColor(.red)
                Spacer()
                TextField("", value: $account.balance, format: .currency(code: "USD"))  // <-- here
                    .textFieldStyle(RoundedBorderTextFieldStyle())
            }
        }
        Button("Save") {
            // for testing
            viewModel.accounts.forEach { print("--> name: \($0.name) balance: \($0.balance)")}
        }
    }
}

// for testing
struct Account: Identifiable {
    let id = UUID()
    var name: String
    var balance: Double  // <-- here
}
  • Related