1

I'm learning Swift through a project. I'm creating an app where the user can create multiple accounts and register transactions within each account. The models and views that are relevant to my question are shown below.

When the user taps on an account, they are taken to the transactions view which shows all the transactions for that selected account. The user can then add a new transaction to the account.

What's not clear to me is how to pass the selected account to the TransactionView and the AddTransactionView when using a NavigationStack. The code I currently have throws the error: Cannot convert value of type 'Bindable<Account>' to expected argument type 'Binding<Account>'

If I change Bindable to .constant that seems to work, but I'm not sure if that's correct.

Would appreciate an explanation of why what I have doesn't work as well.

Models

import Foundation
import SwiftData

@Model
final class Account {
    var created: Date
    var name: String
    var transactions: [Transaction]

    init(created: Date, name: String, transactions: [Transaction] = []) {
        self.created = created
        self.name = name
        self.transactions = transactions
    }
}
import Foundation
import SwiftData

@Model
final class Transaction {
    var date: Date
    var value: Decimal?

    init(date: Date, value: Decimal? = nil) {
        self.date = date
        self.value = value
    }
}

Views

import SwiftData
import SwiftUI

struct AccountsView: View {
    @Query private var accounts: [Account]
    @State private var isAddingAccount = false

    var body: some View {
        NavigationStack {
            List {
                ForEach(accounts) { account in
                    NavigationLink(destination: TransactionsView(account: Bindable(account))) {
                        Text(account.name)
                    }
                }
            }
            .navigationTitle("Accounts")
            .toolbar {
                ToolbarItem {
                    Button {
                        isAddingAccount = true
                    } label: {
                        Label("Add Account", systemImage: "plus")
                    }
                }
            }
        }
        .fullScreenCover(isPresented: $isAddingAccount) {
            AddAccountView(isPresented: $isAddingAccount)
        }
    }
}
import SwiftUI

struct TransactionsView: View {
    @Binding var account: Account
    var transactions: [Transaction] { account.transactions }
    @State private var isAddingTransaction = false

    var body: some View {
        NavigationStack {
            List {
                ForEach(transactions) { transaction in
                    NavigationLink(destination: Text(transaction.date.description)) {
                        Text(transaction.value!.formatted())
                    }
                }
            }
            .toolbar {
                ToolbarItem {
                    Button {
                        isAddingTransaction = true
                    } label: {
                        Label("Add transaction", systemImage: "plus")
                    }
                }
            }
        }
        .fullScreenCover(isPresented: $isAddingTransaction) {
            AddTransactionView(isPresented: $isAddingTransaction, account: $account)
        }
    }
}
import SwiftUI

struct AddTransactionView: View {
    @Binding var isPresented: Bool
    @Binding var account: Account
    @State private var newTransaction = Transaction(date: Date())

    var body: some View {
        NavigationView {
            VStack {
                Form {
                    DatePicker(selection: $newTransaction.date, displayedComponents: .date) { Text("Date") }
                        .datePickerStyle(.compact)

                    TextField(value: $newTransaction.value, format: .number) {
                        Text("Value")
                    }
                }
                Button("Add") {
                    addTransaction(transaction: newTransaction)
                    isPresented = false
                }
            }
            .navigationTitle("Add transaction")
            .navigationBarItems(trailing: Button("Cancel") {
                isPresented = false
            })
        }
    }

    private func addTransaction(transaction: Transaction) {
        withAnimation {
            account.transactions.append(transaction)
        }
    }
}
9
  • You should use Bindable and not Binding for your model types. But in this case it looks like you don’t need Bindable at all since you aren’t modifying the Account object in the child view Apr 14 at 18:41
  • you need another @Query for transactions
    – malhal
    Apr 14 at 19:48
  • does this SO post answer your question: stackoverflow.com/questions/76464884/… Apr 14 at 22:40
  • @JoakimDanielson I do modify the transactions property of the Account object in the grandchild's view - i.e., AddTransactionView Apr 15 at 19:45
  • @workingdogsupportUkraine no because I'm using Bindable as the answer there suggests, and it doesn't work. Apr 15 at 19:47

1 Answer 1

2

I would suggest you read up more look at some tutorials on how to use @Bindable so you properly understand this.

You should declare properties in your view using Bindable and not Binding so it should be

@Bindable var account: Account

and when calling a view with such a property you do not need to do anything special with the value passed

NavigationLink(destination: TransactionsView(account: account))

As for your actual code you do not need to use @Bindable at all when adding transactions to an account since the Account object isn't updated really. This might be a bit confusing but when adding a transaction to an account object you are updating the relationship between them and not the account object itself.

So with that said we can remove @Bindable for the Account property in both the child views and change the declaration to

let account: Account

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.