FetchedResultsController with transient attribute for comparing dates - ios

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

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")

You could try the following NSPredicate, it‘s based on another question and essentially casts the dates to NSNumber, subtracts them and then compares the result to be bigger or smaller than 3600 seconds.
I haven‘t tried it, but maybe it helps. The original question was in ObjC though, so maybe some things changed.
NSPredicate(format: “departureDate != nil AND arrivalDate != nil AND CAST(departureDate, ‘NSNumber‘) - CAST(arrivalDate, ‘NSNumber‘) >= 3600“)

Related

Powering Master-Detail with CloudKit

I have a UISearchResultsController that searches locally while typing, and remotely (CloudKit) when the search button is pressed. The number of results returned needs to be 10-30 (I'm currently testing my app with 25 and it's always enough)
The search results list is populated with RecordType1, while it's detail is populated by RecordType1 and RecordType2. My question is how to go about fetching the second reference type while minimizing my requests/sec. I was looking at Apple's CloudCaptions sample, and they solve the problem by fetching the second record type when each of RecordType1 is fetched. It seems that this needlessly creates fetch requests (1[RecordType1] + 25[RecordType2] = 26 requests). How can I reduce this? It seems like it should be possible in two requests (one for RecordType1 and then one to fetch all the RecordType2 associated with it).
UPDATE: RecordType2 has a back reference to RecordType1
Unless I am misunderstanding your problem, I think you can just execute a query on your CloudKit database:
let searchKey = ... // value for recordType1
let z = CKRecordZone.default()
let predicate = NSPredicate(format: "recordType1 == %#", searchKey)
let query = CKQuery(recordType: "recordType2", predicate: predicate)
db.perform(query, inZoneWith: z.zoneID) { (records, error) in
if error != nil {
// `records` contains recordType2
} else {
// check for errors
}
}
You can also search for a multiple keys using the IN comparison in the predicate:
let searchKeys = ... // [value1, value2, value3, etc.]
let predicate = NSPredicate(format: "recordType1 IN %#", searchKeys)
References
CloudKit CKQueryOperation (Apple)
CloudKit CKQuery (Apple)

NSPredicate on multiple to-many relationships in Core Data

I have an NSPredicate which includes multiple aggregate filters, which is throwing an exception.
I have the following core data model:
I want to pick those ApparelItems for which any of the colours has an rgb of 13576743, and for which all of the picks have a pickTime earlier than a given NSDate.
My code to create the predicate is:
let request = NSFetchRequest(entityName: "ApparelItem")
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "ANY colours.rgb = 13576743"))
// find the NSDate representing midnight x days ago
let cal = NSCalendar.currentCalendar()
if let xDaysAgo = cal.dateByAddingUnit(.Day, value: -2, toDate: NSDate(), options: [])
{
let midnightXDaysAgo = cal.startOfDayForDate(xDaysAgo)
predicates.append(NSPredicate(format: "(ALL picks.pickTime < %#)", midnightXDaysAgo))
}
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
let searchData = try? objectContext.executeFetchRequest(request)
I get the following exception:
Exception name=NSInvalidArgumentException, reason=Unsupported predicate (ANY colours.rgb == 13576743) AND ALL picks.pickTime < CAST(479347200.000000, "NSDate")
I have tried:
Each individual predicate works fine. Ie ANY colours.rgb = ... works, also ALL picks.pickTime < ... works. They just don't work when combined into the same query.
Combining the two using into a single query, linked with AND, rather than using NSCompoundPredicate. Result is the same.
Is it possible that core data simply doesn't support filtering on more than one to-many relationship? That would seem odd. In which case how should I do the above?
Probably could try SUBQUERY() for NSPredicate.
The code below I came out from some guess and not very sure if it works or not. Usually it takes me trial and error many times for a to-many query clause with NSPredicate.
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "SUBQUERY(colours, $colour, ANY $colour.rgb = 13576743).#count > 0"))
let cal = NSCalendar.currentCalendar()
if let xDaysAgo = cal.dateByAddingUnit(.Day, value: -2, toDate: NSDate(), options: []) {
let midnightXDaysAgo = cal.startOfDayForDate(xDaysAgo)
predicates.append(NSPredicate(format: "SUBQUERY(picks, $pick, ALL $pick.pickTime < %#).#count > 0", midnightXDaysAgo))
}
Colors and picks are two different entities, so you should be able to filter by both without using SUBQUERY. One predicate or a compound predicate should both be fine.
From the error message it seems that perhaps something is wrong with the date. Please check you have the expected data type in your managed object subclasses. If you used "primitive values" when creating the subclasses, you will need NSTimeInterval rather than NSDate.

getting specific object in fetchedObjects without iteration

My app has a custom object SSSchedule that I persist in CoreData, with a sortDescriptor of "date" (SSSchedule has a variable var date : NSDate?). Is there a more efficient method to finding a specific SSSchedule object with a specific date rather than iterating through the fetchedObjects array checking each for schedule.date == myDate as! NSDate?
My app references the fetchedObjects quite often, so I would imagine constantly mapping fetchedObjects to a dictionary of type [String : SSSchedule] (for example) every time the context is saved would affect performance...
Write a fetch request to return the objects matching that specific date from the datastore. If you are being consistent, then from what you've written you'll get back an array with one element.
Let Core Data do that searching for you. That's what it's for.
I think if you use a plist which has a Dictionary of Dictionaries. it could be a more what you need.
The first Dictionary will have a key of a tuple of (Day,Month,Year) which can be easily extracted from NSDate. and a value of a Dictionary which key is a tuple of (Hours, Minutes) also extracted from NSDate and a value of String which is the task to do at that time.
this way if you have a specific date, that date is the key to access only the tasks and events you have during that specific date in O(1) time complexity.
Now if you want to know if you have something at a specific time you access it in a similar way. The method is supposed to return String?. If there's a task at a specific time, it will return the task, otherwise it will return nil which means you're free at this time.
This is how the data structure should look [(Day, Month,Year):[(Hours,Minutes):String]]
Regarding extracting components from NSDate
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour |.CalendarUnitMinute
|.CalendarUnitYear|.CalendarUnitMonth|.CalendarUnitDay, fromDate: date)
let day = components.day
let month = components.month
let year = compononets.year
let hour = components.hour
let minutes = components.minutes
Use filter() to build a new array with any objects that match your criteria:
let newArray = fetchedObjects.filter() { $0.date == myDate as! NSDate }
Then check the count of newArray and handle accordingly - unless you know the dates are unique, there could be zero, one or more elements in the array.

Core Data NSPredicate For Property of type Array

i have this Core Data object
class Foo: NSManagedObject {
#NSManaged var YearRange: AnyObject? // [2000,2015]
}
i need to know if 2005 in in the range of the NSManagedObject property YearRange.
this is my fetch:
let request :NSFetchRequest = NSFetchRequest(entityName:"Foo")
let Predicate = NSPredicate(format: "xxx", "xxx")
request.predicate = Predicate
var error: NSError? = nil
var matches: AnyObject = self.managedObjectContext!.executeFetchRequest(request, error: &error)!
if (error == nil && matches.count > 0){
println(matches)
}
what is the NSPredicate format that can help me ?
Since CoreData does not natively support array attributes, I assume YearRange is defined as a Transformable. If that is the case, and assuming you are using a SQLLite store, you cannot use a predicate based on YearRange to filter the fetch request. (Transformables are stored in the SQL database as NSData blobs, and are only transformed back into the appropriate object types when loaded into memory. For the SQLLite store, CoreData compiles any predicate into the fetch SQL and evaluates the result in the database itself. Hence the predicate cannot be based on Transformables.)
You have two options: 1) you could fetch all the objects, and then filter them in memory, or 2) amend your model to store the YearRange as CoreData native types (ie. strings, numbers, dates, etc).
If the elements of the YearRange array represent the start and end years of a range, then I recommend the latter option, with two integer attributes: yearRangeStart and yearRangeEnd. If YearRange might contain several discrete years, then the first option is probably best.

Using "timeIntervalSinceNow" in a predicate in SWIFT

I am trying to query for records that are created in the last 1 hour using Cloudkit (in SWIFT)
I have tried:
let predicate = NSPredicate(predicateWithFormat : "timeIntervalSinceNow %# < %f", "creationTime", -3600)
without success. The error message refers to a parsing error.
Anyone has a clue what would be the right format?
n.b creationTime is the name of the field in the Table
Your predicate isn't valid. Take a look at Apple's guide for creating NSPredicates.
You should use a key path on the left hand side of the expression. I assume that you have an array of objects, and these objects have a property named creationTime. Then the predicate should look like this:
NSPredicate(format: "creationTime.timeIntervalSinceNow > %d", -3600)
Note that I have used "greater than" > operator instead of < in the predicate you posted. That's because a date from the past will return a negative value from timeIntervalSinceNow method, as documentation states.
EDIT:
Seems you can't use key paths in CloudKit predicates. But I think you can create a reference date (an hour ago) and use it for comparison predicate:
let now = NSDate()
if let anHourAgo = NSCalendar.currentCalendar().dateByAddingUnit(
.HourCalendarUnit,
value: -1,
toDate: now,
options: NSCalendarOptions(0)) {
let predicate = NSPredicate(format: "creationDate > %#", anHourAgo)
let filtered = records.filteredArrayUsingPredicate(predicate!)
}

Resources