Reputation: 25
There is a TabView, on different tabs from Swiftdata, data are loaded and somehow modified (@Transient fields) for a particular View
On one of the tabs there is NavigationLink, which leads to the page of creating an element I use .isAutoSaving: false
enum mode {
case create, update
}
struct EditAccount: View {
@Environment(\.dismiss) var dismiss
@Environment(\.modelContext) var modelContext
@Query var currencies: [Currency]
@Bindable var account: Account
var oldAccount: Account = Account()
var mode: mode
init(_ account: Account) {
mode = .update
self.oldAccount = account
_account = .init(wrappedValue: account)
}
init(accountType: AccountType) {
mode = .create
_account = .init(wrappedValue: Account(
id: UInt32.random(in: 10000..<10000000),
type: accountType
)
)
}
var body: some View {
Form {
Section {
TextField("Account name", text: $account.name)
TextField("Budget", value: $account.budget, format: .number)
.keyboardType(.decimalPad)
}
Section {
if mode == .create {
Picker("Currency", selection: $account.currency) {
ForEach(currencies) { currency in
Text(currency.isoCode)
.tag(currency as Currency?)
}
}
}
}
Section {
Button("Save") {
Task {
dismiss()
switch mode {
case .create:
await createAccount()
case .update:
await updateAccount()
}
}
}
}
.frame(maxWidth: .infinity)
}
.navigationTitle(mode == .create ? "Create account" : "Update account")
}
func createAccount() async {
do {
let id = try await AccountAPI().CreateAccount(req: CreateAccountReq(...)
account.id = id
try modelContext.save()
} catch {
modelContext.rollback()
}
}
func updateAccount() async {
do {
try await AccountAPI().UpdateAccount(req: UpdateAccountReq(
id: account.id,
accounting: oldAccount.accounting == account.accounting ? account.accounting : nil,
// same code
)
try modelContext.save()
} catch {
modelContext.rollback()
}
}
}
When I launch this code, I load other tabs and put print() on the functions where objects are modifying for View, I see in the console for each change in the new (Mode = .create) account, how can I change this behavior?
Also, if I choose Currencies in a new account picketer, then even before saving this account in the next tab in the list of accounts, my account will appear, which has not yet been saved
I want to make sure that I have not pressed on Save, the new element does not exist in the database, do I have the opportunity to make such behavior?
Because with each change in the amount or currency in the picker I feel the freezing of the interface, because other screens are recalculated
Also, please tell me how you can make a copy of the database element before changing for comparison whether there were changes in a specific field or not (this is necessary for the implementation of logic PATCH HTTP method)?
Upvotes: 1
Views: 399
Reputation: 51920
The reason why you directly see new objects before saving is because you are using the same ModelContext object in all of your views. So the way to handle the edit (or creation) of objects is to use a separate ModelContext
in the view used for editing.
So remove the @Environment
property in the EditAccount
and replace it with an ordinary property
private let modelContext: ModelContext
Then you create an instance in the init from the apps main ModelContainer object
init(_ account: Account, modelContainer: ModelContainer) {
modelContext = ModelContext(modelContainer)
modelContext.autosaveEnabled = false
//...
}
Since we have a separate context new you should load the account from this context in the init
init(_ account: Account, modelContainer: ModelContainer) {
modelContext = ModelContext(modelContainer)
modelContext.autosaveEnabled = false
mode = .update
guard let model = modelContext.model(for: account.persistentModelID) as? Account else {
// this should never happen since we are not dealing with concurrency here
// so it's up to you if you want some better error handling
fatalError()
}
self.account = model
}
And now nothing will happen to your main context until you do
try modelContext.save()
in this view.
In my solution for this I only pass the id of the object to the init but since you want to compare the account for changes I guess this way is better and you should just store a reference to the old account as you do now
self.oldAccount = account
But remember to do the comparison before you call save() :)
Upvotes: 2