Updating Switfui View When Coredata Changes

Updating Switfui View when CoreData changes

All CoreData Objects are ObservableObjects so they need to be wrapped in @ObservedObject so the View can be refreshed when there are changes

@ObservedObject var selectedNode : Node

Also, make sure you are using @FetchRequest or NSFetchedRequestController so the store can be observed for changes. NSFetchRequest does not observe the store.

SwifUI: How to get manual list to refresh when updating CoreData records?

OK it turned out to be really quite simple. All I actually had to do was remove some of the @Published and provide a UUID for the repeatedPerson record (and for == and hash).

import SwiftUI
import CoreData

let persistentContainerQueue = OperationQueue()
let firstNames = ["Michael", "Damon", "Jacques", "Mika", "Fernando", "Kimi", "Lewis", "Jenson", "Sebastion", "Nico"]
let lastNames = ["Schumacher", "Hill", "Villeneuve", "Hakkinen", "Alonso", "Raikkonen", "Hamilton", "Button", "Vettel", "Rosberg"]

class RepeatedPerson: ObservableObject, Hashable
{
var id: UUID = UUID()
var index: Int
var person: Person?

init (person: Person, index: Int)
{
self.person = person
self.index = index
}

func hash(into hasher: inout Hasher)
{
hasher.combine(id)
}

static func == (lhs: RepeatedPerson, rhs: RepeatedPerson) -> Bool
{
return lhs.id == rhs.id
}
}

class RepeatedPeople: ObservableObject
{
@Published var people: [RepeatedPerson] = []
}

func getRepeatedPeople() -> [RepeatedPerson]
{
var repeatedPeople:[RepeatedPerson] = []

let records = allRecords(Person.self)
for person in records
{
for index in 1...3
{
repeatedPeople.append(RepeatedPerson(person: person, index: index))
}
}
return repeatedPeople
}

struct ContentView: View
{
@Environment(\.managedObjectContext) private var viewContext

@ObservedObject var repeatedPeople = RepeatedPeople()

init()
{
repeatedPeople.people = getRepeatedPeople()
}

var body: some View
{
VStack
{
List()
{
ForEach(repeatedPeople.people, id: \.self)
{ repeatedPerson in
Text("\(repeatedPerson.index)) \(repeatedPerson.person!.firstName!) \(repeatedPerson.person!.lastName!)")
}
}
HStack
{
Button("Add Record", action:
{
addItem()
repeatedPeople.people = getRepeatedPeople()
})
Button("Change Record", action:
{
let q = allRecords(Person.self)
let r = q.randomElement()!
let oldLastName = r.lastName
r.lastName = lastNames.randomElement()!
print ("changed \(r.firstName!) \(oldLastName!) -> \(r.firstName!) \(r.lastName!)")
saveDatabase()
repeatedPeople.people = getRepeatedPeople()
})
Button("Reset Database", action:
{
print ("Reset database")
deleteAllRecords(Person.self)
repeatedPeople.people = getRepeatedPeople()
})
}
}
}

private func addItem()
{
withAnimation
{
let newItem = Person(context: viewContext)
newItem.timestamp = Date()
newItem.firstName = firstNames.randomElement()!
newItem.lastName = lastNames.randomElement()!
print ("added \(newItem.firstName!) \(newItem.lastName!)")
saveDatabase()
}
}
}

func query<T: NSManagedObject>(_ type : T.Type, predicate: NSPredicate? = nil, sort: NSSortDescriptor? = nil) -> [T]
{
let context = PersistenceController.shared.container.viewContext

let request = T.fetchRequest()
if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
if let predicate = predicate
{
request.predicate = predicate
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}

func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
return query(T.self, sort: sort)
}

func deleteAllRecords<T: NSManagedObject>(_ type : T.Type)
{
let context = PersistenceController.shared.container.viewContext
let results = allRecords(T.self)
for record in results
{
context.delete(record as NSManagedObject)
}
saveDatabase()
}

func saveDatabase()
{
persistentContainerQueue.addOperation()
{
let context = PersistenceController.shared.container.viewContext
context.performAndWait
{
try? context.save()
}
}
}

How to update another instance of a model when I save new data using CoreData in another view

To Observe/Listen for CoreData changes you need to use the @FetchRequest wrapper that was made for SwiftUI in the View or use the traditional FetchedRequestController wrapped in an ObservableObject (since you have a CoreData Manager)

The it is a lot of code but the CoreData Programming Guide has a good tutorial on how to create it wrapped in a TableView. The big difference is that instead of updating the TableView you will update a variable in the ObservableObject.

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
//Update an @Published variable here
}

Tutorial Combo Video

Also, you should Observe the QuizViewModel and pass it on appropriately

//In the ContentView
@StateObject var quizViewModel = QuizViewModel(categoryIndex: 1)
//Initialize the Views that need the model like this
AddQuestionView().environmentObject(quizViewModel)

//All Views that need the Model
struct AddQuestionView:View{
@EnvironmentObject var quizViewModel = QuizViewModel
//...
}

You might be able to Maximize the SwiftUI advantages if you look over the Apple SwiftUI tutorials

CoreData: Get notified when NSManagedObject is changed without keeping reference to NSManagedObject

One way would be:

  1. Save the value of the objectID property of the managed object you want to watch, instead of a reference to the managed object.
  2. Use NotificationCenter to add an observer for the NSManagedObjectContextObjectsDidChange notification, which is generated by your managed object context.
  3. When you receive this notification, look at the userInfo dictionary for a key called NSUpdatedObjectsKey. It contains references to any managed objects that have changed. See if any of them have the objectID you saved in step 1.

Depending on how you want things to work, you might prefer to use the NSManagedObjectContextDidSave notification instead. You might also want to use NSInsertedObjectsKey and/or NSDeletedObjectsKey.

Custom Fetch Request not updating the view when adding entries to context

You have to Observe the ticket

Change let ticket: Ticket to

@ObservedObject var ticket: Ticket

All CoreData objects are ObservableObjects



Related Topics



Leave a reply



Submit