SwiftUI - Core Data error logs flooding console - core-data

I have a simple view that displays a list of sorted and filtered items. I'm using FetchRequest to retrieve the items from Core Data and a computed property to return the filtered and sorted results. (Note I can't leverage sortDescriptors here because I'm sorting on a calculated value.)
Here's a snippet from the code:
#FetchRequest(entity: Item.entity(), sortDescriptors: [])
private var items: FetchedResults<Item>
private var listItems: [Item] {
items
.filter(listFilter)
.sorted(by: sortType.sort)
}
In my list, if I reference items directly, all is good but if I reference listItems, the console is littered with the following logs:
CoreData: fault: NULL _cd_rawData but the object is not being turned into a fault
My understanding is that this error typically appears when you reference an object off the context queue so I tried wrapping the listItems calculation in a performAndWait on the first item's context but that did not resolve the issue.
What is the solution here?

Related

Adding to a one-to-many relationship not updating UI

I have a one-to-many relationship in core data of plan -> recipe. plan.recipes is of type NSSet?, so I have created custom NSManagedObject classes with computed properties to convert these into arrays, adding an extra property recipesArray:
public var recipesArray: [Recipe] {
let set = recipes as? Set<Recipe> ?? []
return set.sorted {
$0.wrappedName < $1.wrappedName
}
}
I then display this list in a View using a ForEach, using the recipesArray property. A subview of this view calls plan.addToRecipes(recipe: Recipe), to add a new object to the relationship. I then save.
The issue is, the ForEach in the parent view does not react to this addition. If I refresh the view by navigating away, then the new recipe is shown, but the View is not automatically updated when the new recipe is added.
Does anyone know how to do this? Should I be using the original recipes property instead of this custom array one?
You need to make another FetchRequest for Recipes using a predicate that equals a given plan, e.g. something like this:
struct RecipesView: View {
var fetchRequest: FetchRequest<Recipe>
var recipes: FetchedResults<Recipe> { fetchRequest.wrappedValue }
init(plan: Plan) {
let sortDescriptors = ...
let predicate = NSPredicate(format: "plan = %#", plan)
fetchRequest = FetchRequest(sortDescriptors: sortDescriptors, predicate: predicate, animation: .default)
}
var body: some View {
ForEach(recipes) { recipe in
RecipeView(recipe: recipe) // it's important to not access recipe's properties (firing a fault) until inside a sub-view's body.
}
}
}
Note: There is currently a bug in FetchRequest that results in body always invoked even when this View is init with the same plan and thus same FetchRequest. This is because the FetchRequest struct inits a new object inside it every time, causing it and consequently this View to appear as changed to SwiftUI. I reported this bug so hopefully they fix it. You could workaround it in the meantime with a wrapper View that takes the plan as a let so body won't be called.

performFetch creating diffable data source snapshots with too much data

After executing an NSBatchBatchInsertRequest and calling fetchedResultsController.performFetch(), the frc’s delegate method controller(_:didChangeContentWith:) is called with a NSDiffableDataSourceSnapshot<Section, NSManagedObjectID> that unexpectedly contains all data in Core Data.
However I've only just inserted new data into Core Data. Applying a snapshot with all the data at each performFetch is causing a high memory load and jittery scrolling when I test this with thousands of cells.
Is there a way to make this delegate method only receive a snapshot with incremental updates to the data source?
Code
Here's how the NSFetchedResultsController is created:
let fetchedResultsController = NSFetchedResultsController<MyManagedObject>(
fetchRequest: fetchRequest,
managedObjectContext: persistentContainer.viewContext,
sectionNameKeyPath: nil,
cacheName: nil
)
The NSFetchRequest:
let fetchRequest = MyManagedObject.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "value", ascending: true)]
fetchRequest.fetchBatchSize = 10
The snapshot is applied in this NSFetchedResultsControllerDelegate method below.
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
dataSource.apply(snapshot as NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>, animatingDifferences: false)
}
The most obvious solution to me was implementing this delegate method only, but that method doesn't seem to get called:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith diff: CollectionDifference<NSManagedObjectID>) {
print(diff)
}
Data source to Core Data connection
When the data source is asked to make a cell for a specific NSManagedObjectID, the right MyManagedObject is found for that NSManagedObjectID using fetchedResultsController.managedObjectContext.object(with:).
Then the MyManagedObject is converted into an Item, which is finally used to configure the cell.

How to access a single core data object

In a core data entity containing multiple objects, how to access a specific object outside of using ForEach
I have tried using filtering the results with NSPredicate but that does not work even when only one object fits the description
For example, I want to be able to edit something outside the ForEach
loop, and update it into the latest core data entry
struct ContentView: View {
#FetchRequest(entity: NameData.entity(), sortDescriptors: []) var names : FetchedResults<NameData>
var body: some View{
VStack{
Text("Edit Latest Entry")
// Textfields for editing first and last names
ForEach(self.names){ name in
Text(name.firstName)
Text(name.lastName)
}
}
}
}

How do I transform SwiftUI fetch request results based on related objects?

I am building a SwiftUI list where I need a dynamic predicate. The approach is discussed here: https://www.hackingwithswift.com/books/ios-swiftui/dynamically-filtering-fetchrequest-with-swiftui
Here is my code so far:
struct SomeView: View {
var collection: Collection
var messages: FetchRequest<Message>
init(collection: Collection) {
let predicate : NSPredicate = NSPredicate(format: "collection = %#", collection)
self.collection = collection
self.messages = FetchRequest<Message>(entity: Message.entity(), sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)], predicate: predicate)
}
var body: some View {
List(messages.wrappedValue, id: \.uniqueIdentifier) { message in
// construct the UI
}
}
}
So far so good.
What I can’t figure out how to do: I need to transform the messages elements based on some other messages in the results (let’s say based on previous message for simplicity). messages[0] should look a particular way. messages[1] look depends on messages[0]. messages[2] depends on messages[1] and so on. I cannot precompute this, since it may vary across time. It should be computed in the context of this specific fetch request/result.
I could express this as some transient computed property on the Message object, which the view code could then use to branch out. I could have a function where I give a particular message and the array of messages, the function looks up the message and other messages and sets the state of a given message based on that. However, SwiftUI limits what I can do in View code, I can’t execute functions this way.
I can run map or flatmap where I access the wrappedValue, but those don’t let me access other elements of the collection to make decisions (I think?).
How would I run this kind of transformation in this context?
If I correctly understood your description (and taking into account that FetchedResults is a RandomAccessCollection) I would go with the following approach
var body: some View {
List(messages.wrappedValue, id: \.uniqueIdentifier) { message in
rowView(for: message, from: messages.wrappedValue)
}
}
func rowView(for message: Message, from result: FetchedResults<Message>) -> some View {
// having .starIndex, .endIndex, .position, etc. do any dependent calculations here
// and return corresponding View
}

Non-optional to-many relationship allows nil or empty set

I have a sample data structure:
Table "Groups" and table "Items". Groups can contains zero or more items and item must be linked to minimum one or more groups:
Groups |--------- to-many, optional ---------->>|Items
---------|<<------- to-many, non-optional! -------|--------
groupName| |itemName
items | |groups
I create NSManagedObject subclasses. Both are trivial but here is Items:
class Items:NSManagedObject {
#NSManaged var itemName:String
#NSManaged var groups:Set<Groups> //attention: there is no ! or ?
}
In code below I expecting to catch error:
var item = Items()
item.itemName = "AAAA"
do {
try Items.moc.save() //moc - static field in Items "linked" to ManagedObjectContext
}
catch {
print(error)
}
But there is no errors! item saves to CoreData with empty groups. I can't figure out why? But if I make Items to Groups relation non-optional to-one (in class Items groups field became #NSManaged var group:Groups) exception throws as I expected.
I know, that I can implement in Items class function validateGroups, where I can check if groups nil or empty, but I want to know: is there my mistake that I cant find or it is Core Data bug (or feature)? More, I like to implement storage logic into database (in sql I very like triggers, I can't live without foreign keys, constraints etc).
So, please, help me to understand this. Thanks!
Problem solved, thanks to Willeke!
When I set minimum count in Item's entity relation properties problem disappeared.

Resources