swiftmacrospredicateswiftdataswift-optionals

Getting a compiler error trying to write a complicated Predicate


I know there are similar questions out there, like this one for example Combining Predicate in SwiftData but all that looks unnecessary complex to me and I find it difficult to believe that there is no easier way.

This is what I need:

let aPred = #Predicate<Event> {
            $0.dateStart != nil &&
            ($0.dateStart >= startOfDay && $0.dateStart <= endOfDay) ||
            ($0.dateEnd >= startOfDay && $0.dateEnd <= endOfDay) ||
            ($0.dateStart < startOfDay && $0.dateEnd >= startOfDay)
}

Event is a SwiftData model which has 2 optional date properties dateStart and dateEnd which I need to compare against 2 dates startOfDay and endOfDay

You probably know that this will give a compiling error:

The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions.

In Core Data we could easily do this with a single NSPredicate like this:

let nsPred = NSPredicate(format: "dateStart != nil && (dateStart >= %@ && dateStart <= %@) || (dateEnd >= %@ && dateEnd <= %@) || (dateStart < %@ && dateEnd >= %@)",
                                argumentArray: [startOfDay, endOfDay,
                                                startOfDay, endOfDay,
                                                startOfDay, startOfDay])

Or if needed, we could break that down into smaller predicates and then use NSCompoundPredicate with .and or .or

There should be a simple way of achieving the same in SwiftData without all those complex macros, queries and predicate xpressions.

I have to keep each item optional as this is required by CloudKit.

Uploaded minimal working project here. See ViewController.swift.


Solution

  • The issue

    This is because you are comparing an Optional<Date> against a Date.


    The solution

    Try to get rid of the optionality somehow like extending the Optional, so it can be compared:

    extension Optional: Comparable where Wrapped: Comparable {
        public static func < (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
            guard let lhs, let rhs else { return false }
            return lhs < rhs
        }
    }
    

    👉 Here is a working demo with optional predicate 👈