Hello,
my goal is it to implement an Edit Sheet of a Model with Discard changes in SwiftData.
- Xcode Version 15.0 beta 6 (15A5219j)
Approaches
In the following modelContext
refers to the context fetched from the environment by the View. Also code for showing the sheet (eg. toggling isPresented) is omitted.
// App
ContentView()
.modelContainer(for: Model.self, isAutosaveEnabled: true, isUndoEnabled: true)
// In Content View
@Query var models: [Model]
//...
ForEach(models) { model in
ModelView(model: model)
//eg. longpresgesture that calls openEditSheet
.sheet(..., content: {
EditModelView(model: model)
}
}
Disabling autosave + rollback
// In ContentView
func openEditSheet() {
modelContext.autosaveEnabled = false
}
// In EditModelView
func discardEditSheet() {
modelContext.rollback()
modelContext.autosaveEnabled = true
}
func saveEditSheet() {
try? modelContext.save() // probably not needed if autosave gets enabled anyway
modelContext.autosaveEnabled = true
}
However this approach does not work, as SwiftData continues to save anyway and therefore there is nothing to rollback.
modelContext in Memory
// In ContentView
let context = ModelContext(modelContext.container)
context.autosaveEnabled = false
context.container.configuration.removeAll()
context.container.configuration.insert(ModelConfiguration(..., inMemory: true)
//...
EditModelView()
.modelContext(context)
// Also tried: .enviroment(\.modelContext, context)
// In EditModelView
func discardEditSheet() {
modelContext.rollback() // probably not needed as the container is never saved
}
func saveEditSheet() {
try? modelContext.save() // move to persistent storage from memory
}
- The idea was to use something like
parent
in CoreData. However as this is (currently) not supported. - Also tried this by modifying the
modelContext
directly (instead of creating a new context) and itscontainer
directly or before creating the context. - The child can not be a
ModelContainer
as you can not pass a context to it or set itsmainContext
(get-only).
However this approach does not work, as the container does not seem to stay in memory. Maybe related to previous.
BackingData
EditModelView(model: Model(backingData: model.persistentBackingData)
- not sure about this one, played around a little with it but not sure what it means / should mean and how I would expect it to work. I have never been great at cooking :).
UndoManager (preferred)
// In ContentView
func openEditSheet() {
modelContext.undoManager?.beginUndoGrouping()
}
// In EditModelView
func discardEditSheet() {
modelContext.undoManager?.endUndoGrouping()
modelContext.undoManager?.undoNestedGroup()
// Also tried adding: try? modelContext.save()
}
func saveEditSheet() {
modelContext.undoManager?.endUndoGrouping()
}
This approach does works kinda weird:
- The
ModelView
of the updated model inContentView
'sForEach
is not updated regarding the undo action. - However when I re-open the
EditTaskView
of the updated model it has the expected state, even though the model to edit is passed by theContentView
(which does not have the correct state?). - After relaunching the app or when modifying the model again (this time not undoing) the previous sate changes are recognised
Therefore this looks like it does what I want, with the ContentView having the correct state, but not displaying it. Only reason I can think of is the Model: Observable
not getting triggered and therefore no View update.
Conclusion
After playing around with the above approaches and combining them in every possible way, i think that:
- disabling autosave at runtime is currently not working and this is a bug (otherwise
autosaveEnabled
should be get-only). - UndoManager does not trigger a View update of a
Model
/Observable
and this is a bug
Would be happy to hear other opinions on this. Am I missing something here? Am I ******?
Question
However as my conclusion does not fix my problem I am wondering:
- Are my approaches (theoretically) correct?
- How do I fix / workaround the UndoManager issue? If I identified it correctly, how can I manually notify the view that a
Observable
model has changed?