Updating Switfui View when CoreData changes
All CoreData Objects are ObservableObject
s 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:
- Save the value of the
objectID
property of the managed object you want to watch, instead of a reference to the managed object. - Use
NotificationCenter
to add an observer for theNSManagedObjectContextObjectsDidChange
notification, which is generated by your managed object context. - When you receive this notification, look at the
userInfo
dictionary for a key calledNSUpdatedObjectsKey
. It contains references to any managed objects that have changed. See if any of them have theobjectID
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
Tableview Accessories Don't Load Correctly
How to Call Swiftui Navigationlink Conditionally
Create a Weak Container in Swift That Accepts a Native Swift Protocol
What the Difference of Keys and Values in Dictionary of Swift
How to Integrate Mapbox Sdk with Swiftui
What Does This Mean? Variable Declared Followed by a Block Without Assignment
Swift 3:Delegate Within Tapgesturerecognizer in Generics Doesn't Get Called
Swiftui - Navigation View Opening with Back Button and Half Grey Screen/Weird Behavior
Swift: Call Self Method Inside Init
Deleterowsatindexpaths Crashing
Accessing Struct from One Class to Another
Swift Access Array with Index Gives Following Error. Any Idea Why
Rotation Gesture Produces Undesired Rotation
Swift Generics and Protocols Not Working on Uikit [Possible Bug]
Swift 2: Multiline Mkpointannotation
Cannot Invoke "+=" with an Argument List of Type (Int, @Value Int)
.Sink Is Not Returning the Promise Values from a Future Publisher