Search code examples
core-dataswiftuinspredicate

Filter a list of CoreData items based on an array property using NSPredicate


Context

I have a Core Data entity called 'Task'

'Task' has a relationship with another entity called 'Tag' : @NSManaged public var tags: NSSet? This data is stored as:

public var tagsArray: [Tag] {
    let set = tags as? Set<Tag> ?? []

    return set.sorted {
        $0.wrappedTagTitle < $1.wrappedTagTitle
    }
}

I am trying to filter a list of tasks on the basis if they contain a certain tag, i.e. with a title of "School".

var fetchRequest: FetchRequest<Task>

init(tagFilterName: String) {
    let tag = Tag()
    tag.title = tagFilterName
    fetchRequest = FetchRequest<Task>(entity: Task.entity(), sortDescriptors: [], predicate: NSPredicate(format: "tagsArray CONTAINS %@", tag))
}

This is initialised in the parent view:

FilteredScrollView(tagFilterName: "School")

(Excuse the haphazard code in the init, I am just trying to get across what I am trying to do.)

Result

As it stands this crashes on runtime. Any help would be greatly appreciated.


Solution

  • The immediate problem is that you're calling Tag() to create a new instance, which doesn't call the designated initializer. To create a new Tag you would need to use Tag(context: NSManagedObjectContext). You're getting a crash because you're trying to use an object that wasn't properly initialized.

    That's only part of the problem though. If you fix that, you'll fix the crash but you'll get zero results. The other problem is that you shouldn't be creating a new Tag for the fetch. Core Data predicates look for exact matches-- there's nothing like Equatable, and it won't look for some other Tag that has the same title. It's going to look for that exact tag. But since you just created that tag and since it's not connected to any tasks, you'll get zero results. The CONTAINS operator won't match anything since no Task contains the Tag you just created.

    To match on a property value on through a relationship-- in this case, matching Tag.title via the tags relationship-- your predicate would look like

    NSPredicate(format: "any tags.title = %@", tagFilterName)
    

    When fetching Task, this asks for any Task where any member of the tags relationship has a value equal to tagFilterName.