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.
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
}
}
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)
}
}
}
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