I have the following code which does what I want, "Cancel" restores the initial values of the book. But is there a better way to do this? Having to save individually each field of a book (there could be a lot of them) doesn't look good. Using the whole book as a @State
doesn't work, I get the compilation error Cannot assign to property: 'self' is immutable
when trying to update the whole book at once in restoreBook()
.
If I understand this correctly, I need the initialValues to be @State
so they are kept even if the view is rebuilt.
import SwiftUI
import SwiftData
@Model
class Book {
var title: String
var author: String
init(title: String, author: String) {
self.title = title
self.author = author
}
}
struct BookDetail: View {
@Bindable var book: Book
@State private var initialTitle: String = ""
@State private var initialAuthor: String = ""
@Environment(\.dismiss) private var dismiss
var body: some View {
Form {
TextField("Title", text: $book.title, axis: .vertical)
TextField("Author", text: $book.author, axis: .vertical)
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
dismiss()
}
}
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
restoreBook()
dismiss()
}
}
}
}
func restoreBook() {
book.title = initialTitle
book.author = initialAuthor
}
init(book: Book) {
self.book = book
self._initialTitle = State(wrappedValue: book.title)
self._initialAuthor = State(wrappedValue: book.author)
}
}
On approach to solve this that focuses more on SwiftData than SwiftUI is to work with a local ModelContext object in the view and make use of the possibilities to manually save or rollback any changes.
First change the property declarations
@Environment(\.modelContext) private var modelContext
@State private var localContext: ModelContext?
@State var book: Book
Note that depending on how your ModelContainer is declared you wont' need the modelContext
property if you can access the model container in a global way but here I assume you can't.
Then we set things up in onAppear
where we create the new local context and also load the Book
object we are going to work with from this local context since we don't want to change anything in the main context.
.onAppear {
self.localContext = ModelContext(modelContext.container)
localContext?.autosaveEnabled = false
self.book = localContext?.model(for: book.id) as! Book //Replace book from main context with one from the local context
}
Then we need to either change or rollback the changes in the buttons
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
try? localContext?.save()
dismiss()
}
}
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
localContext?.rollback()
dismiss()
}
}