NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: Core Data: NSPredicate with array values as argument

Forums > SwiftUI

I'm trying to write a NSPredicate to filter my Students based on their tags.

Student CoreData entity has an NSSet of Tags (to many relationship):

extension Student {
   @NSManaged public var tags: NSSet?

And Tag CoreData entity has the inverse to-many relationship as well:

extension Tag {
   @NSManaged public var students: NSSet?

I use this request to select tags for filtering, based on a query String:

let dataController = DataController.shared
let request: NSFetchRequest<Tag> = Tag.fetchRequest()
request.predicate = NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(Tag.title), query)
let moc = dataController.container.viewContext
let fetchedTags = try? moc.fetch(request)

I get this way an optional array of tags ([Tag]?), or an empty one. That's working ok.

But, and that is the key point of my question, now I'm trying to use this array of tags to filter which students have that tags, this way:

tagPredicate = NSPredicate(format: "%K IN %@", #keyPath(Student.tags), fetchedTags!)

But it fails, with the error "NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate".

I'm trying to use different argument sintaxes, as detailed here: https://nspredicate.xyz, but neither "SELF CONTAINS", nor "IN" options don't work.

Does anybody knows how to get it working?

Thanks!

   

hi Óscar,

once you have a Tag object, say var tag: Tag, there's no need to go back directly to Core Data to get students associated with the object; those students are just

let students: Set<Student> = (tag.students as? Set<Student>) ?? []

if you have an optional array of Tags, which you called fetchedTags, then you can find all associated students for each element of the array

let associatedStudents: [Set<Student>] = fetchedTags?.compactMap({ $0 as? Set<Student> }) ?? []

finally, if you want to accumulate all of these sets together into a single Set of students, you could use reduce, starting with an empty set and then adding in (by union) each element of the associatedStudents array.

let studentCollection = associatedStudents.reduce(Set<Student>()) { $0.union($1) }

now (i think i have the syntax right ... it's my first code-writing exercise of the day!), you have all students who are associated with one of more of the fetchedTags.

hope that helps,

DMG

   

Thanks! Great information for me! I'm now reorganizing many parts of my app, thanks to your first line sintax, avoiding so many "FetchRequests" to filter data, and using your approach. Fantastic!

In the meanwhile, making advances with tag filtering, fot the second line you suggest:

let associatedStudents: [Set<Student>] = fetchedTags?.compactMap({ $0 as? Set<Student> }) ?? []

I get the "error" (just yellow indicator):

Cast from 'Tag' to unrelated type 'Set<Student>' always fails

Maybe is this the correct sintax? (Googling your proposals):

let associatedStudents: [Set<Student>] = fetchedTags?.compactMap { item in
        return item.students
    } as! [Set<Student>]

   

hi,

sorry ... as i said, first code writing exercise of the day ... i knew something was probably missing.

it should be

let associatedStudents = fetchedTags?.compactMap({ $0.students as? Set<Student> }) ?? []

it's not clear to me that you need to include the explicit type annotation that i suggested, but you can check.

hope that's right (typing from my iPad),

DMG

   

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.