I am working on a SwiftUI app that uses SwiftData for data persistence.
I've implemented a ContentViewModel that handles fetching, creating, updating, and deleting operations on SwiftData entities.
The issue I'm facing is that while updates to entities are reflected immediately in the UI, create and delete operations do not update the UI until the app is restarted, even though the underlying data changes are successful and persisted.
Below is the simplified code for my ViewModel and View:
ContentViewModel:
import Combine
import Foundation
import SwiftData
import SwiftUI
@Observable
class ContentViewModel {
var modelContext: ModelContext
var items: [Item] = []
var showingSheet: Bool = false
var name: String = ""
var content: String = ""
var editingItem: Item?
init(modelContext: ModelContext) {
self.modelContext = modelContext
self.items = fetchData()
}
func fetchData() -> [Item] {
do {
var fetchDescriptor = FetchDescriptor<Item>()
fetchDescriptor.sortBy = [SortDescriptor<Item>(\.dateAdded, order: .forward)]
let items = try modelContext.fetch(fetchDescriptor)
return items
}
catch {
fatalError(error.localizedDescription)
}
}
func newItem() {
editingItem = nil
showingSheet = true
}
func startEditing(_ item: Item) {
editingItem = item
name = item.name
content = item.content
showingSheet = true
}
func clearFields() {
name = ""
content = ""
editingItem = nil
}
func addNewItem() {
let newItem = Item(name: name, content: content)
insert(newItem)
showingSheet = false
}
func insert(_ item: Item) {
modelContext.insert(item)
save()
}
func update(_ item: Item, newName: String, newContent: String) {
item.name = newName
item.content = newContent
item.dateAdded = Date()
save()
showingSheet = false
}
func delete(_ item: Item) {
modelContext.delete(item)
save()
}
func save() {
do {
try modelContext.save()
}
catch {
print(error.localizedDescription)
}
}
}
ContentView:
import SwiftData
import SwiftUI
struct ContentView: View {
@State var viewModel: ContentViewModel
init(modelContext: ModelContext) {
let viewModel = ContentViewModel(modelContext: modelContext)
_viewModel = State(initialValue: viewModel)
}
var body: some View {
NavigationStack {
List {
ForEach(viewModel.items) { item in
VStack(alignment: .leading) {
HStack(alignment: .top) {
Text(item.name)
.bold()
Spacer()
Text(item.dateAdded.formatted(date: .numeric, time: .shortened))
.font(.caption)
.foregroundStyle(.gray)
Button(action: {
viewModel.startEditing(item)
}) {
Image(systemName: "pencil")
.foregroundColor(.blue)
}
Button(action: {
viewModel.delete(item)
}) {
Image(systemName: "trash")
.foregroundColor(.red)
}
}
Text(item.content)
.font(.subheadline)
}
}
}
.navigationTitle("To Do List")
.toolbar {
ToolbarItem(placement: .automatic) {
Button {
viewModel.newItem()
} label: {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $viewModel.showingSheet, onDismiss: {
viewModel.clearFields()
}) {
Form {
Section {
TextField("Add the title...", text: $viewModel.name)
} header: {
Text("Title")
}
Section {
TextEditor(text: $viewModel.content)
} header: {
Text("Content / Notes")
}
Section {
if viewModel.editingItem != nil {
Button("Update Item") {
if let editingItem = viewModel.editingItem {
viewModel.update(editingItem, newName: viewModel.name, newContent: viewModel.content)
}
}
.disabled(viewModel.name.isEmpty || viewModel.content.isEmpty)
}
else {
Button("Add Item") {
viewModel.addNewItem()
}
.disabled(viewModel.name.isEmpty || viewModel.content.isEmpty)
}
}
}
}
}
}
}
I ensure that the list of items is published and that I fetch the items again after each create/delete operation.
However, the UI does not refresh to show these changes immediately after they occur.
Interestingly, update operations do refresh the UI immediately.
Question: What could be causing the UI to not update immediately after create/delete operations on SwiftData entities in a SwiftUI app, and how can I ensure that these changes are reflected in the UI without needing to restart the app?
Thanks.