Search code examples
swiftcore-datanspredicatensfetchedresultscontroller

Can I create a predicate based on the properties of child class objects?


I have two classes: Activity and Action. Activity is the parent class, Action are the child, this is a one to many relationship. In setting up a NSFetchedResultsController I would like to set a predicate based the properties of the child class. Here's an example...

fetchRequest.predicate = NSPredicate(format: "filter only activities that have actions which have their date property equalling today")

If I were to filter the activities using a for in loop, this is what it would look like...

for activity in activities
{
    if activity.actions != nil
    {
        for action in activity.actions
        {
            if action.date == today
            {
                // add activity to filtered array
            }
        }
    }
}

Solution

  • You can use a SUBQUERY in your NSPredicate. Here I used an NSArray as a demonstration.

    let today = Date()
    
    let array: NSArray = [
        Activity(actions: [
            Action(date: today)
            ]),
        Activity(actions: [
            Action(date: Date(timeIntervalSince1970: 0))
            ])
    ]
    
    // here is your predicate
    let result = array.filtered(using: NSPredicate(format: "SUBQUERY(actions, $action, $action.date == %@) .@count > 0", today as CVarArg))
    print(result.count) // 1
    

    Note that I used == here to compare the dates, which means it will only match if the date is exactly equal to today. If by "today" you meant "any time in the 24 hours", then you would have to use the answer suggested in this question:

    func getLastAndNextMidnight(date: Date) -> (Date, Date) {
        let dateComponents = Calendar.current.dateComponents([.year, .month, .day], from: date)
        var oneDay = DateComponents()
        oneDay.day = 1
        let lastMidnight = Calendar.current.date(from: dateComponents)!
        let nextMidnight = Calendar.current.date(byAdding: oneDay, to: lastMidnight)!
        return (lastMidnight, nextMidnight)
    }
    let midnights = getLastAndNextMidnight(date: today)
    let result = array.filtered(using: NSPredicate(format: "SUBQUERY(actions, $action, ($action.date >= %@) AND ($action.date <= %@)) .@count > 0", midnights.0 as CVarArg, midnights.1 as CVarArg))
    print(result.count)