Best way to filter a day with NSPredicateEditor

91 views Asked by At

Did anyone come around a solution for filtering data ( CoreData ) by a day, using NSPredicateEditor ? The idea is to make it most convenient to the user. The standard solution would be to define 2 criteria for date :

  1. one for >= start of the day
  2. another one for <= end of the day.

One EditorRowTemplate should simply look like:

  • left expression = aDate (property of a Core Data entity )

  • rightexpression = Dates

enter image description here

Then, the app should convert the predicate to somewhat like:

"aDate >= '3.5.20210 00:00:00' AND aDate <= '3.5.20210 23:59:59'".

Of course, it should take the value from the date which the user has entered in the row template.

I thought, closures can be a way. So to say, creating the NSPredicate programmatically. But how to use it in NSExpression and grabbing the date from the input ?

The desired row template should look like this :

enter image description here

2

There are 2 answers

0
Willeke On BEST ANSWER

The row template can convert the predicate in predicate(withSubpredicates:), no other overrides required. In IB the right expressions are Dates.

override func predicate(withSubpredicates subpredicates: [NSPredicate]?) -> NSPredicate {
    // call super to get the predicate, for example aDate == '3.5.20210 14:03:53'
    let predicate = super.predicate(withSubpredicates: subpredicates)
    // convert the predicate to aDate >= '3.5.2021 00:00:00' AND aDate < '4.5.2021 00:00:00'
    var newPredicate = predicate
    if let comparisonPredicate = predicate as? NSComparisonPredicate,
        let predicateDate = comparisonPredicate.rightExpression.constantValue as? Date {
        let keyPath = comparisonPredicate.leftExpression.keyPath
        var components = Calendar.current.dateComponents([.year, .month, .day], from: predicateDate)
        components.hour = 0
        components.minute = 0
        components.second = 0
        components.calendar = NSCalendar.current
        switch comparisonPredicate.predicateOperatorType {
            case .lessThan:
                // aDate < '3.5.2021 00:00:00'
                let date = components.date! as NSDate
                newPredicate = NSPredicate(format: "%K < %@", keyPath,date)
            case .lessThanOrEqualTo:
                // aDate < '4.5.2021 00:00:00'
                components.day = components.day! + 1
                let date = components.date! as NSDate
                newPredicate = NSPredicate(format: "%K < %@", keyPath,date)
            case .greaterThan:
                // aDate >= '4.5.2021 00:00:00'
                components.day = components.day! + 1
                let date = components.date! as NSDate
                newPredicate = NSPredicate(format: "%K >= %@", keyPath,date)
            case .greaterThanOrEqualTo:
                // aDate >= '3.5.2021 00:00:00'
                let date = components.date! as NSDate
                newPredicate = NSPredicate(format: "%K >= %@", keyPath,date)
            case .equalTo:
                // aDate >= '3.5.2021 00:00:00' AND aDate < '4.5.2021 00:00:00'
                let startDate = components.date! as NSDate
                components.day = components.day! + 1
                let endDate = components.date! as NSDate
                newPredicate = NSPredicate(format: "%K >= %@ AND %K < %@", keyPath, startDate, keyPath, endDate)
            case .notEqualTo:
                // NOT (aDate >= '3.5.2021 00:00:00' AND aDate < '4.5.2021 00:00:00')
                let startDate = components.date! as NSDate
                components.day = components.day! + 1
                let endDate = components.date! as NSDate
                newPredicate = NSPredicate(format: "NOT (%K >= %@ AND %K < %@)", keyPath, startDate, keyPath, endDate)
            default:
                newPredicate = predicate
        }
    }
    return newPredicate
}
0
Christian Krueger On

Thanks to Willeke's answer I was able to solve it. I want to let you participate on the entire solution.

  1. Create a custom class CustomRowTemplate: NSPredicateEditorRowTemplate
  2. override func predicate, see Willeke's code above. This has access to the date you wish to filter and it creates the predicate with a date/time range for the day.
    override func predicate(withSubpredicates subpredicates: [NSPredicate]?) -> NSPredicate
  1. In the attribute inspector, for the predicateEditor , create a new Rowtemplate for keypath and date. then change its class to CustomRowTemplate

  2. That's all.