I am attempting to include a function in a Predicate definition. Is this possible?
Let's say you have a Core Data entity of Places with attributes for latitude and longitude.
I would like to add annotations to a mapview of those Places within a specified distance from the user location. I can, of course, loop through the entire database and calculate the distance between each Place and the user location but I will have about 35000 places and it would seem that it would be more efficient to have a predicate in the fetchedResultsController setup.
I tried the code below but I receive an error message of "Problem with subpredicate BLOCKPREDICATE(0x2808237b0) with userInfo of (null)"
func myDistanceFilter(myLat : CLLocationDegrees, myLong : CLLocationDegrees, cdLat : CLLocationDegrees, cdLong : CLLocationDegrees) -> Bool {
let myLocation = CLLocation(latitude: myLat, longitude: myLong)
let cdLocation = CLLocation(latitude: cdLat, longitude: cdLong)
if myLocation.distance(from: cdLocation) < 5000.0 {
return true
} else {
return false
}
}//myDistancePredicate
And inside the fetchedResultsController:
let distancePredicate = NSPredicate {_,_ in self.myDistanceFilter(myLat: 37.774929, myLong: -122.419418, cdLat: 38.0, cdLong: -122.0)}
If it is possible to have a block/function inside a predicate how do you get a reference to an attribute for the entity object being evaluated?
Any guidance would be appreciated.
An additional observation for anyone else struggling with a similar issue.
Considering the suggestions of pbasdf and Jerry above, at least in my case, there is no reason why a region has to be round. I'll craft a name indicating a nearly rectangular area. I ran some tests with latitude and longitude values. These latitude and longitude values can be scaled to a rectangle enclosing a circular radius specified by the user. One degree of latitude is about 69 miles and one degree of longitude at the latitude of Chicago is about 51 miles. I used the following:
var myUserLatitude : CLLocationDegrees!
var myUserLongitude : CLLocationDegrees!
In the init file for the view:
guard let userLatitude = locationManager.location?.coordinate.latitude else {return}
guard let userLongitude = locationManager.location?.coordinate.longitude else {return}
myUserLatitude = userLatitude
myUserLongitude = userLongitude
Inside the fetchedResultsController variable creation:
let latitudeMinPredicate = NSPredicate(format: "latitude >= %lf", myUserLatitude - 1.0)
let latitudeMaxPredicate = NSPredicate(format: "latitude <= %lf", myUserLatitude + 1.0)
let longitudeMinPredicate = NSPredicate(format: "longitude >= %lf", myUserLongitude - 1.0)
let longitudeMaxPredicate = NSPredicate(format: "longitude <= %lf", myUserLongitude + 1.0)
var compoundPredicate = NSCompoundPredicate()
compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [anotherUnrelatedPredicate,latitudeMinPredicate,latitudeMaxPredicate,longitudeMinPredicate, longitudeMaxPredicate])
fetchRequest.predicate = compoundPredicate
Obviously I will create another property to scale the 1.0 value per the user desired region. Initial tests seem to work and best of all I can't believe how fast it it. Literally, the tableView is populated by the time the viewController segues to the enclosing mapView.
Well, yes it is possible. NSPredicate does have +predicateWithBlock: and init(block:).
However if you scroll down on that page you see the bad news:
Core Data supports block-based predicates in the in-memory and atomic stores, but not in the SQLite-based store.
So, whether you use an in-memory store, or do the filtering in code, either way you need to bring these 35,000 items into memory, and performance of a brute force approach will be poor.
There is a point of complexity at which SQL is no longer appropriate – you get better performance with real code. I think your requirement is far beyond that point. This will be an interesting computer science project in optimization. You need to do some pre-computing, analagous to adding an index to your database. Consider adding a region attribute to your place entities, then write your predicate to fetch all places within the target location's region and all immediate neighbors. Then filter through those candidates in code. I'm sure this has been done by others – think of cells in a cell phone network – but Core Data is not going to give you this for free.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With