SwiftUI with Core Data: Fetch request with predicate crashes - core-data

I have successfully added Core Data to my SwiftUI project. I need to filter the results by type. When I add a predicate to the fetch request the app crashes at the point where the view containing the fetch request tries to load.
The error is Thread 1: EXC_BAD_ACCESS (code=1, address=0x1)
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Task.entity(),
sortDescriptors:[
NSSortDescriptor(keyPath: \Task.type, ascending: true),
NSSortDescriptor(keyPath: \Task.totalScore, ascending: false),
],
predicate: NSPredicate(format: "type == %#", Int16(1))) //this is the line that crashes the app
var tasks: FetchedResults<Task>
If I change the Int16(1) to Int16(0), the app does not crash but no data appears in the list.
This is the first app I have witten using core data so I need help.

Thanks to andrewbuilder. He got me looking in the right direction. The correct way to use a predicate that expects an Int is:
predicate: NSPredicate(format: "type == %i", Int16(1)))
I was using %# when I should have used %i.

You can also make the int an NSNumber object:
NSPredicate(format: "type == %#", #(1))

Related

SwiftUI - Core Data error logs flooding console

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?

How to use the new nsPredicate dynamic property of #FetchRequest property wrapper with object passed into View

My first attempt was to set the property wrapper's nsPredicate dynamic property in .onAppear, but if the view gets reinitialized for any reason, the predicate set by .onAppear is lost. So I went back to using the init pattern.
Here is what I thought should work (but doesn't) and something that does work (however mysteriously):
struct ItemEditView : View {
var item: Item
#FetchRequest(fetchRequest: Attribute.fetchRequestAllInOrder(), animation: .default)
var attributes: FetchedResults<Attribute>
init(item: Item) {
self.item = item
// This is how I would have expected to set the dynamic property at View initialization, however
// it crashes on this statement
attributes.nsPredicate = NSPredicate(format: "item == %#", item)
// Not sure why the below works and the above does not.
// It seems to work as desired, however it receives this runtime warning:
// "Context in environment is not connected to a persistent store coordinator"
$attributes.projectedValue.wrappedValue.nsPredicate = NSPredicate(format: "item == %#", item)
}
var body: some View {
List {
ForEach(attributes) { attribute in
Text("Name:\(attribute.name) Order:\(attribute.order)")
}
}
}
}
So, why does the first assignment to nsPredicate crash? And after commenting out that first one, why does the second one work? Is the warning message a real issue? Is there a better way to do this? It seems like there should be a simple way to do this using the new dynamic properties.
It turns out that (re)setting the nsPredicate property of the #FetchRequest in onAppear is really the way to go. However, to make this work, you must make sure that your View's init() method does not get called again after onAppear is called. There are several valuable hints on how to accomplish this in the Demystify SwiftUI session from this year's WWDC (WWDC21-10022).

Sorting URLs with FetchRequest crashes when new content is saved

I have an app which uses Core Data with an entity called Item and has the attribute "url" to save URL.
The FetchRequest looks like the code below.
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Item.url, ascending: true)], animation: .default)
The app crashes when creating a item
let newItem = Item(context: viewContext)
newItem.url = URL(string: "https://www.google.com") //crashes after this line
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
with this crash log
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSURL compare:]: unrecognized selector sent to instance 0x2838187e0'
When sorting using other attributes(ex. String) doesn't cause the app to crash.
I have also tried implementing fetchrequest from init()
var fetchedItem: FetchRequest<Item>
init(){
fetchedItem = FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Item.url, ascending: true)], animation: .default)
}
Changing the sort from string to url works, only crashes when creating a new item to save.
Is this a wrong way for implementing? Or is it just not possible to use FetchRequest and Sort by URL together?
Please look closely at the error message
'-[NSURL compare:]: unrecognized selector sent
and please look also at the NSSortDescriptor documentation
You construct instances of NSSortDescriptor by specifying the key path of the property to be compared and the order of the sort (ascending or descending). Optionally, you can also specify a selector to use to perform the comparison, which allows you to specify other comparison selectors such as localizedStandardCompare: and localizedCaseInsensitiveCompare:. Sorting raises an exception if the objects to be sorted do not respond to the sort descriptor’s comparison selector
So the error is obvious: NSURL is not comparable. You need a string like description, absoluteString or just the host (the google.com part)
One solution is to create an extension of (NS)URL like in Asperi's answer.
An easier solution is to create the NSSortDescriptor with a string key (path) rather than a Swift key path.
#FetchRequest(sortDescriptors: [NSSortDescriptor(key: "url.host", ascending: true)], animation: .default)
Try with the following extension to URL (or NSURL)
extension URL {
public func compare(_ other: URL) -> ComparisonResult {
self.absoluteString.compare(other.absoluteString)
}
}

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
}

iOS 7 CoreData - FetchedProperty returns faulting array

maybe someone could tell me if i am on the right way.
I have two entities:
Exercise with following attributes:
title - string ; exercise_id - int
AddedExercise with following attributes:
count - int ; added_exercise_id - int
The AddedExercise has a fetched property called link_exercise with Destination: Exercise
and predicate: exercise_id == $FETCH_SOURCE.added_exercise_id
In my code i do the following:
for(AddedExercise *e in listOfAddedEx){
[moc refreshObject:e mergeChanges:YES];
NSLog(#"%#", e.link_exercise);
}
And the log says:
Relationship fault for (), name link_exercise, isOptional 1, isTransient 1, entity AddedExercise, renamingIdentifier link_exercise, validation predicates (
), warnings (
), versionHashModifier (null)
userInfo {
}, fetchRequest (entity: Exercise; predicate: (exercise_id == $FETCH_SOURCE.added_exercise_id); sortDescriptors: ((null)); type: NSManagedObjectResultType; ) on 0x1700d8b80
So it seems like something is there, represented in a fault.
But if i try to access the "link_exercise" array. For example with:
e.link_exercise.count or e.link_exercise.lastObject
I'll get the following error:
'NSUnknownKeyException', reason: '[<_NSCoreDataTaggedObjectID 0xd000000000040002> valueForUndefinedKey:]: this class is not key value coding-compliant for the key added_exercise_id.'
Maybe someone has an idea how to solve that.
Many thanks in advance.
S.R.
-----> UPDATE 1:
I changed now the predicate to:
SELF.exercise_id=exercise_id
Now i can access the Exercise object but i get a wrong id, because
SELF.exercise_id=exercise_id should look like SELF.exercise_id=added_exercise_id
With predicate SELF.exercise_id=added_exercise_id i get the following error:
'NSInvalidArgumentException', reason: 'Unable to generate SQL for predicate (exercise_id == added_exercise_id) (problem on RHS)'
-----> UPDATE 2:
Still cannot figure out why it isn't working...
For me it seems like
SELF.exercise_id == $FETCH_SOURCE.added_exercise_id
should be the right predicate, but it doesn't work.
Maybe someone else has some suggestions...?
Or should i create a fetch request by myself (programmatically) ?
That means set up a fetch request to all "Exercise"s and give the one back where exercise_id = added_exercise_id. This is basically that, what i currently try to solve through a fetched property.
-----> UPDATE 3:
Ok i nearly have it!! One thing i forgot to mention is, that i use a multi managedObjectContext core data stack (Including a worker-, main- master-context)
masterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
masterContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
[masterContext setPersistentStoreCoordinator: coordinator];
mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
mainContext.parentContext = masterContext;
workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
workerContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
workerContext.parentContext = mainContext;
All my request go to the worker-context (in this case the fetched proper doesn't work)
BUT if all my requests go to the master-context the fetched property works as expected!!
So it seems like it only works with the managedObjectContext which has the persistent store.
Can somebody explain me, why this cannot work with an other managedObjectContext?

Resources