Search code examples
iosswiftcore-datansfetchedresultscontroller

FetchedResultsController with transient attribute for comparing dates


I have a JOURNEY entity in CoreData. It has arrivalDate and departureDate as attributes.

arrivalDate and departureDate both are of type NSDate?

I want to fetch only those journey whose duration is more than an hour.

To achieve this I created a transient attribute in my Journey+CoreDataClass

public var isJourneyLongEnough: Bool{
    if let arrival = arrivalDate as Date?, let departure = departureDate as Date?{
        let duration = departure.timeIntervalSince(arrival)
        return duration >= 3600 ? true : false
    } else{
        return false
    }
}

When I tried fetching it crashes with an error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath isJourneyLongEnough not found in entity.

On researching more about this issue I figured out that we can't use transient property in predicate as these doesn't exist when fetching managed objects from persistentStore.

You cannot fetch using a predicate based on transient properties (although you can use transient properties to filter in memory yourself). ... To summarize, though, if you execute a fetch directly, you should typically not add Objective-C-based predicates or sort descriptors to the fetch request. Instead you should apply these to the results of the fetch.

What is the best way to achieve this? I am using a FetchedResultsController to display all journeys in a UITableView


Solution

  • Timestamps are stored as seconds (since the reference date Jan 1, 2001) in the Core Data SQLite file, and one can simply check if the difference between departure and arrival date is at least 3600 seconds:

    let predicate = NSPredicate(format: "arrivalDate - departureDate >= 3600")
    fetchRequest.predicate = predicate
    

    This worked as expected in my test. If the minimal journey duration is determined at runtime, use

    let minDuration = 3600
    let predicate = NSPredicate(format: "arrivalDate - departureDate >= %@", minDuration as NSNumber)
    

    Remark: The above predicates work only if used in a Core Data fetch request. The following variant works both with Core Data fetch requests and with directly evaluated predicates:

     NSPredicate(format: "arrivalDate.timeIntervalSinceReferenceDate - departureDate.timeIntervalSinceReferenceDate >= 3600")