LHS and RHS both keypaths - core-data

Trying to find active guestCards where a prospectiveTenant matches all of the search queries. Possibilities are firstName, lastName, phoneNumber.
prospectiveTenants is a to-many on guestCard.
This code:
NSString *predicateString = [NSString stringWithFormat: #"active=1"];
if (self.searchHeader.firstNameTextField.text.length > 0) {
predicateString = [predicateString stringByAppendingFormat:#" AND (ANY prospectiveTenants.firstName CONTAINS[cd] %#)", self.searchHeader.firstNameTextField.text];
}
if (self.searchHeader.lastNameTextField.text.length > 0)
{
predicateString = [predicateString stringByAppendingFormat:#" AND (ANY prospectiveTenants.lastName CONTAINS[cd] %#)", self.searchHeader.lastNameTextField.text];
}
if (self.searchHeader.phoneNumberTextField.text.length > 0)
{
predicateString = [predicateString stringByAppendingFormat:#" AND (ANY prospectiveTenants.phone CONTAINS[cd] %#)", self.searchHeader.phoneNumberTextField.text];
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
[self.fetchedResultsController.fetchRequest setPredicate:predicate];
produces this error
'unimplemented SQL generation for predicate : (ANY prospectiveTenants.lastName CONTAINS[cd] S) (LHS and RHS both keypaths)'

Gah.
So it looks like predicateWithFormat does special work to make sure quotes are inserted around strings. Should have been obvious.

In order to be able to use the work the NSPredicate init does, try building up the format string and the values to be placed in the string as two variables and then pass them both when creating the NSPredicate.
This has the advantage of taking care of any other special work NSPredicate might like to do.
Re-writing in Swift, this would become:
var predicateString = "active=1"
var predicateParameters = [Any]()
if !self.searchHeader.firstNameTextField.text.isEmpty {
predicateString.append(" AND (ANY prospectiveTenants.firstName CONTAINS[cd] %#)")
predicateParameters.append(self.searchHeader.firstNameTextField.text)
}
if !self.searchHeader.lastNameTextField.text.isEmpty {
predicateString.append(" AND (ANY prospectiveTenants.lastName CONTAINS[cd] %#)")
predicateParameters.append(self.searchHeader.lastNameTextField.text)
}
if !self.searchHeader.phoneNumberTextField.text.isEmpty {
predicateString.append(" AND (ANY prospectiveTenants.phone CONTAINS[cd] %#)")
predicateParameters.append(self.searchHeader.phoneNumberTextField.text)
}
let predicate = NSPredicate(format: predicateString, argumentArray:predicateParameters)
self.fetchedResultsController.fetchRequest.predicate = predicate

Related

Swift5 Simple Core Data NSPredicate Get

I'm simply trying to access a record in core data by a property I've named "id" that is of String type. The following keeps complaining about 'Unable to parse the format string "id == 8DF3F2C6741B47C8864D1052C36E2C4D"'. How can I solve this issue?
private func getEntity(id: String) -> NSManagedObject? {
var myEntity: NSManagedObject?
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "MyEntity")
fetchRequest.predicate = NSPredicate(format: "id = \(id)")
do {
var tempArray = try getCurrentManagedContext().fetch(fetchRequest)
myEntity = tempArray.count > 0 ? tempArray[0] : nil
} catch let error as NSError {
print("get failed ... \(error) ... \(error.userInfo)")
}
return myEntity
}
It’s a format string. Instead of
NSPredicate(format: "id = \(id)")
Write
NSPredicate(format: "id == %#", id)

Core data Fetch request with predicate swift 3

I know how to fetch from core data with predicates using a
let predicate = NSPredicate(format: "MyEntityAttribute == %#", "Matching Value"). I want to know if it's possible to fetch all of the values for a particular attribute without using a Matching Value. I want to get a count of the total number of values for a particular attribute.
This is what I got so far, but I am only getting back what is matching the name attribute.
let filter = "wayne"
let fetchRequest = NSFetchRequest<Likes>(entityName: "Likes")
let predicate = NSPredicate(format: "name == %#", filter)
fetchRequest.predicate = predicate
do {
let nameCount = try context.fetch(fetchRequest)
if nameCount.count >= 0 {
print("name exist")
}
} catch{
print(error.localizedDescription)
}
I took a different approach and use NSFetchRequestResult to get back the result of the single attribute.
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Likes")
fetchRequest.resultType = .dictionaryResultType
fetchRequest.propertiesToFetch = ["name"] // Single attribute I wanted to fetch
fetchRequest.returnsDistinctResults = true
do {
let result = try context.fetch(fetchRequest)
let resultDic = result as! [[String:String]]
print(resultDic.count)
print(resultDic)
} catch{
print(error.localizedDescription)
}

NSFetchedResultsController and to-many relationship not working

Ok, I was searching and trying in that case for the last 1-2 weeks and I didn't get it work. I would be able to achieve what I want without NSFRC but for performance reasons and convienience I would like to do it with the NSFRC.
So, I have a DataModel with 2 Entities - see the picture
There is one Account and one account can have many accountchanges - which is quite obvious.
So I want to be able to choose an Account and then show all AccountChanges for that specific Account.
So far I was able to get the Account and also accessing the NSSet in cellForRow Function but I am not getting the correct sections and numberOfRowsInSection - this is the main issue.
Here is some code:
func numberOfSections(in tableView: UITableView) -> Int {
print("Sections : \(self.fetchedResultsController.sections?.count)")
if (self.fetchedResultsController.sections?.count)! <= 0 {
print("There are no objects in the core data - do something else !!!")
}
return self.fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Section Name")
print(self.fetchedResultsController.sections![section].name)
let sectionInfo = self.fetchedResultsController.sections![section]
print("Section: \(sectionInfo) - Sections Objects: \(sectionInfo.numberOfObjects)")
return sectionInfo.numberOfObjects
}
There are some print statements which are only for information!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = myTable.dequeueReusableCell(withIdentifier: "myCell")! as UITableViewCell
let accountBalanceChanges = self.fetchedResultsController.object(at: indexPath)
print("AccountBalanceChanges from cell....")
print(accountBalanceChanges)
let details = accountBalanceChanges.accountchanges! as NSSet
print("Print out the details:")
print(details)
let detailSet = details.allObjects
let detailSetItem = detailSet.count // Just for information!
let myPrint = detailSet[indexPath.row] as! AccountChanges
let myVal = myPrint.category
myCell.textLabel?.text = myVal
return myCell
}
So, I am able to get the data but always only one item and not the whole set - I guess due to the fact that the sections/ numberOfRows are wrong.
Here is my NSFRC
var fetchedResultsController: NSFetchedResultsController<Accounts> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<Accounts> = Accounts.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let predicate = NSPredicate(format: "(ANY accountchanges.accounts = %#)", newAccount!)
fetchRequest.predicate = predicate
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
return _fetchedResultsController!
}
I am assuming it is the SortDescriptor or the predicate - or maybe both?
Any help or at least directions are well appreciated.
I already tried many different approaches but none was giving me the correct results.
I would do the opposite, I mean using the FRC to fetch all the changes for an account with a certain Id, and use the following predicate:
let predicate = NSPredicate(format: "accounts.aId = %#", ACCOUNTID)
or
let predicate = NSPredicate(format: "accounts = %#", account.objectID)
I would rename Accounts entity to Account and same for the relationship since it's a to-one relationship.
That's assuming you have a table view with all the accounts and when you click on one it gives you back its changes.
var fetchedResultsController: NSFetchedResultsController<AccountChanges> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<AccountChanges> = AccountChanges.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let predicate = NSPredicate(format: "accounts.aId = %#", ACCOUNTID)
fetchRequest.predicate = predicate
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
return _fetchedResultsController!
}
Cheers

Ambiguous use of 'subscript'

xcode started complaining about the following code (worked fine w/o any issues for quite a few months):
let request = NSFetchRequest(entityName: entity)
request.returnsDistinctResults = true
let nsArr: NSArray = ["int_field", "date_field"]
request.propertiesToFetch = nsArr as [AnyObject]
request.resultType = .DictionaryResultType
let predicate = NSPredicate(format: "(val1 == %d) AND (val2 == %#)", argumentArray: [anIntegerValue, aStringValue])
request.predicate = predicate
do {
let results: NSArray = try moc.executeFetchRequest(request)
for result in results {
Up to this point all is fine. But when I try to extract data from a result, I get an 'Ambiguous use of 'subscript' error on the following:
let val1 = result["int_field"] as! Int
let val2 = result["date_field"] as! NSDate
Not sure what is happening, but if I replace the above syntax with:
let val1 = (result as! NSDictionary) ["int_field"] as! Int
let val2 = (result as! NSDictionary)["date_field"] as! NSDate
it works. What I am not clear about is why declaring the following is not sufficient (or what happened in ios 9.2 to cause this):
request.resultType = .DictionaryResultType
Thoughts?
Please use Swift native collection types, they are so much easier to maintain.
First of all, assign the array directly to propertiesToFetch. No NSArray, no [AnyObject]
request.propertiesToFetch = ["int_field", "date_field"]
Your result is an array of dictionaries which have String keys and Int / NSDate values. Multiple types must be represented by AnyObject.
do {
let results = try moc.executeFetchRequest(request) as! [[String:AnyObject]]
Now, since the compiler knows that the array contains dictionaries this will smoothly compile
for result in results {
let val1 = result["int_field"] as! Int
let val2 = result["date_field"] as! NSDate
}
}
Foundation collection types don't contain the type information and can cause those Ambiguous use messages.
After a close look, the proposed solution by Vadian did not work for me.
The only thing that worked was my original solution:
let val1 = (result as! NSDictionary) ["int_field"] as! Int
let val2 = (result as! NSDictionary)["date_field"] as! NSDate
It seems that the trigger was importing Realm framework (to address another dependency). All I can say is that when I removed Realm, the error/issue goes away.
This occurs despite me not actually using Realm at the moment.

replacing objects in array while enumerating?

I am sending an update for my app that will require that the users databases be updated. I am storing data in a property list. Basically a every point in the array are NSMutableDictionaries and I need to add keys, replace keys etc.
I attempted the following, however it generates an NSException,
for (NSMutableDictionary *dict in myArray) {
if ([dict objectForKey:#"someKey"] == nil) {
//Extract the value of the old key and remove the old key
int oldValue = [[dict objectForKey:#"key1"] intValue];
[dict removeObjectForKey:#"key1"];
[dict setValue:[NSString stringWithFormat:#"%d pts", oldValue] forKey:#"newKey"];
//Add new keys to dictionnary
[dict setValue:#"some value" forKey:#"key2"];
[dict setValue:#"some value" forKey:#"key3"];
[dict setValue:#"some value" forKey:#"key4"];
[self.myArray replaceObjectAtIndex:index withObject:dict];
}
What should I do to update my data in the above manner?
The problem is that you cannot modify the array you are iterating over with fast enumeration.
The code snippet has no need for that replaceObjectAtIndex:withObject: call at all, as you replace the object with the very same object! So if you remove that line everything should work.
Generally, you can avoid similar problems if you use the plain old for loop with indexing, i.e.
for (int i = 0; i < [array count]; i++) {
id obj = [array objectAtIndex:i];
// ...
}
as this will not mess up with fast enumeration.
Create a copy of the array and enumerate through the copy. In this way you can safely modify the original one:
for (id obj in [NSArray arrayWithArray:entries]) {
[entries removeObject:obj];
}
Do not use:
for (int i = 0; i < [array count]; i++) {
id obj = [array objectAtIndex:i];
[array removeObject:obj];
}
Do this because, after the removal the array indexes will be offset!
First, make sure that myArray is an NSMutableArray. If so, you'll probably see some error if you debug the code that says something like _NSArrayI unrecognized selector sent to instance _NSArrayI means it's an immutable array. This is very annoying, but try to test by doing this. You can then just replace your myArray with the mutableArray.
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:self.myArray];
for (NSMutableDictionary *dict in mutableArray) {
if ([dict objectForKey:#"someKey"] == nil) {
//Extract the value of the old key and remove the old key
int oldValue = [[dict objectForKey:#"key1"] intValue];
[dict removeObjectForKey:#"key1"];
[dict setValue:[NSString stringWithFormat:#"%d pts", oldValue] forKey:#"newKey"];
//Add new keys to dictionnary
[dict setValue:#"some value" forKey:#"key2"];
[dict setValue:#"some value" forKey:#"key3"];
[dict setValue:#"some value" forKey:#"key4"];
[mutableArray replaceObjectAtIndex:index withObject:dict];
}
}

Resources