Seong Lee Seong Lee - 7 months ago 24
Swift Question

Filter on relationship after fetching data

I need to display a recipe list from Recipe entity that is filtered on category attribute which sets a relationship to RecipeCategory entity.

The following diagram should give you the structure.

enter image description here

I have

var recipes = [Recipe]()
var filteredRecipe = [Recipe]()


And filteredRecipe will have recipe objects that are filtered by the value of
name
attribute of RecipeCategory entity.

Below is a working code that works with filtering on title attribute.
This approach works because title is an attribute of Recipe entity.

filteredRecipes = recipes.filter({$0.title!.rangeOfString(lowercased) != nil})


But if I try to filter on category relationship (NSManagedObject) using
valueForKey()
like so

filteredRecipe = recipes.filter({$0.category!.valueForKey("name")!.rangeOfString(lowercased) != nil})


It won't filter and will return the full recipe list instead.

I've seen people using Predicate but I want to avoid making new fetch request every time. Instead I want to filter on the fetched data. How can I filter values on relationship attribute and get the filtered result of the main object (Recipe entity)?

Answer

You can use a predicate without fetching from the store. NSPredicate can be used against a collection as well as an NSFetchRequest.

You could also write the filter out:

let filteredRecipe = recipes.filter({
    if let category = $0.category {
        if let name = category.valueForKey("name") {
            return name.rangeOfString(lowercased) == nil
        } else {
            return false
        }
    } else {
        return false
    }
})

Personally, I would use a predicate instead:

let predicate = NSPredicate(format: "category.name in %@", ["Main", "Desert", "Side"])
let filtered = recipes.filter({predicate.evaluateWithObject($0)})

Update

Went and played with the filter a little more in a test project. Here is the code with no NSManagedObject subclasses so valueForKey and optional casting is used judicially:

let filteredRecipe = recipes?.filter({
    if let category = $0.valueForKey("category") as? NSManagedObject {
        if let name = category.valueForKey("name") as? String {
            return name.rangeOfString(lowercased) == nil
        } else {
            return false
        }
    } else {
        return false
    }
})