NSArrayController's fetchPredicate not working as expected - macos

I write an app that uses Core Data to save friends. An NSTableView is supposed to show all friends, separated from each other depending on their status (online, offline, marked as favourite).
To fetch the specific friends for a section I use an NSArrayController.
In the following code you see what predicates I set on the specific array controllers.
_onlineFriendsController.fetchPredicate = [NSPredicate predicateWithFormat:#"(favourite = NO || favourite = nil) && id in %#", self.onlineFriendsID];
_offlineFriendsController.fetchPredicate = [NSPredicate predicateWithFormat:#"(favourite = NO || favourite = nil) && not (id in %#)", self.onlineFriendsID;
The _onlineFriendsController works as expected: It fetches the friends that are not favourited but online. However, the _offlineFriendsController doesn't work. It fetches all friends that are not favourited despite their availability.
I have also tried to set a predicate with a block to be able to debug it a bit better.
_offlineFriendsController.fetchPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
if (![[evaluatedObject valueForKey:#"favourite"] boolValue]) {
if (![self.onlineFriendsID containsObject:[evaluatedObject valueForKey:#"id"]]) {
return YES;
}
}
NSLog(#"%#", [evaluatedObject valueForKey:#"name"]);
return NO;
}];
This returns NO for everyone who is online. It turns out that _offlineFriendsController fetched all friends anyway. What am I doing wrong? I have only set the controller's managedObjectContext, the sortDescriptors, the fetchPredicate and the entityName.
What is the difference between filterPredicate and fetchPredicate anyways? filterPredicate just screws everything without actually doing something.
Any help is much appreciated, thanks.

Related

NSTreeController - Retrieving selected node

I added Book object in bookController (NSCreeController). Now i want to get stored Book object when i select the row.
- (IBAction)addClicked:(id)sender {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSUInteger indexArr[] = {0,0};
Book *obj = [[Book alloc] init];
NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterLongStyle];
obj.title = [NSString stringWithFormat:#"New %#",dateString];
obj.filename = [NSString stringWithFormat:#"%d",arc4random()%100000];
[self.booksController insertObject:obj atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
}
I concede there perhaps could be a better solution--
I am unfamiliar with how NSTreeController works, but I looked a the class reference and noticed that it has a content property, similar to an NSArrayController (Which I am familiar with grabbing specific objects from).
I believe that if the content property is actually of type of some kind of tree data structure, my answer here probably won't work. The class reference says this about content:
The value of this property can be an array of objects, or a
single root object. The default value is nil. This property is
observable using key-value observing.
So this is what I historically have done with the expected results:
NSString *predicateString = [NSString stringWithFormat:NEVER_TRANSLATE(#"(filename == %#) AND (title == %#)"), #"FILENAME_ARGUMENT_HERE", #"TITLE_ARGUMENT_HERE"];
NSArray *matchingObjects = [[self content] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:predicateString]];
Then simply calling -objectAtIndex: will grab you your object. Note that the NSArray will be empty if the object doesn't exist, and if you have duplicate objects, there will be multiple objects in the array.
I also searched for an answer to your question, and found this SO thread:
Given model object, how to find index path in NSTreeController?
It looks pretty promising if my solution doesn't work, the author just steps through the tree and does an isEqual comparison.
If you could (if it's not too much trouble), leave a comment here to let me know what works for you, I'm actually curious :)

Core Data Relationship Designing - Load only desired entity

I need help in designing the database for my application using Core Data.
Namely:
I have 2 Entites called Verse and Translation.
Verse has one-to-many relationship with translation table.
Translation table contains translations in different languages such as EN,DE,AR.
I know if i load the Verse table then corresponding translations will also be lazy loaded.
But i want to load only desired translation of each Verse. E.g only EN.
Because if i load 1000 Verse and with 5 different Translation (DE,EN,TR,AR,FR) then i have a big amount of data which i want to avoid >> 1000x5 = 5000.
I don‘t want load everytime the translations which i dont need. Only the selected translation.
thank you for any advice.
Regards,
Core data does something called faulting, it should only load data you actually ask for. It might load some metadata for all those objects, but it won't actually load the translation until you ask for it. I would suggest looking into faulting more if you want to know more than that because it seems fairly complex. At least the rest of what faulting is seems complex.
i found a solution which is exactly what i wanted. I tested and it returns only the desired Translations.
- (NSArray *) loadAllVersesByLanguage
{
NSManagedObjectContext *_managedObjectContext = [self managedObjectContext];
NSArray *fetchedObjects;
NSString *turkish = #"TR";//should be parameterized
NSString *arabic = #"AR";
NSPredicate *pred = [NSPredicate predicateWithFormat:#"languageCode==%# OR languageCode==%#",turkish,arabic];
NSFetchRequest *fetch = [[NSFetchRequest alloc] initWithEntityName:#"Verse"];
NSError * error = nil;
fetchedObjects = [_managedObjectContext executeFetchRequest:fetch error:&error];
if(fetchedObjects != nil && fetchedObjects.count > 0 ){
for(Verse *verse in fetchedObjects){
NSSet *verseSet = [verse.translations filteredSetUsingPredicate:pred];
[verse.translations setSet:verseSet];
}
return fetchedObjects;
}else{
return nil;
}
return nil;
}

avoid duplicate results on Core Data fetch

I have a subclass of the CoreDataTableViewController (subclass of UITAbleViewController dome by the people on Stanford done to link CoreData and TableViews). On this Class, I want to perform a fecth, sorting by an attribute called "definition" and the code which executes it is the following:
- (void)setupFetchedResultsController{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:self.entity];
request.propertiesToFetch=[NSArray arrayWithObject:#"definition"];
request.returnsDistinctResults=YES;
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"%K != nil", #"definition"];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"%K != ''", #"definition"];
NSPredicate *predicate3= [NSPredicate predicateWithFormat:#"%K contains[cd] %#", #"definition", self.seachBar.text];
NSArray *prepredicateArray;
if ([self.seachBar.text length]) {
prepredicateArray = [NSArray arrayWithObjects:predicate1, predicate2, predicate3,nil];
}else {
prepredicateArray = [NSArray arrayWithObjects:predicate1, predicate2,nil];
}
request.predicate=[NSCompoundPredicate andPredicateWithSubpredicates:prepredicateArray];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"definition" ascending:YES ]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
If I understood it correctly, setting request.returnsDistinctResults=YES; should avoid fetching duplicates. However it doesn't work and I'm seeing duplicates of this attribute's value.
Is there something I'm missing there? I'd appreciate some pointings there. Thank you in advance.
EDIT: If anyone is having the same issue here, after applying David's answer the resulting fetchedResultsController is just a NSDIctionary with object with only the requested value, which for displaying only purposes is quite fine. One thing I've done in cellForRowAtIndexPath in order to display the results on the cell label is:
Before:
HNMR *hnmr = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text=hnmr.definition;
After:
cell.textLabel.text=[[self.fetchedResultsController objectAtIndexPath:indexPath] valueForKey:#"definition"];
From the documentation of returnsDistinctResults:
This value is only used if a value has been set for propertiesToFetch.
From the documentation of propertiesToFetch:
This value is only used if resultType is set to NSDictionaryResultType.
From the documentation of resultType:
The default value is NSManagedObjectResultType.
This all tells me that the propertiesToFetch is ignored because you haven't set the resultType yourself and the default it to return managed objects instead of dictionaries. Since the propertiesToFetch is ignored the returnsDistinctResults is ignored as well and thus you are still getting duplicates.
Try setting the result type to return dictionaries instead of managed objects.
request.resultType = NSDictionaryResultType;
In addition to David Rönnqvist answer I suggest a useful link (with a sample) on selecting distinct values with Core Data:
core-data-how-to-do-a-select-distinct
Hope that helps.

Update predicate on arraycontroller

I have an array controller and I have bound an entity to it, sort descriptor and predicate.
If I change the predicate format when the app runs, it works, so the binding is working.
My problem is when I want to change the predicate, f.ex. with a search term or some string that a user inputs, nothing happens, but when I add a record to the core data database, the tableview does update.
So my question is, how do I tell the array controller that the predicate has changed and it should update itself. Here is a code that runs when I enter search term, it also works, and I get all the NSLogs output correctly. Just my tableview is not updating itself.
- (IBAction)didChangeSearch:(id)sender {
if (sender == searchField) {
NSString *searchterm = [sender stringValue];
if (searchterm.length > 1) {
predicate = [NSPredicate predicateWithFormat:#"name contains [c]%#", #"m"];
NSLog(#"Putting predicate to the job : %#", searchterm);
} else {
predicate = nil;
NSLog(#"There is nolonger any predicate");
}
}
NSLog(#"I just got %#", [sender stringValue]);
}
I would like to say in the start that I am very new to bindings, have never used them until tonight, got a good feeling for them, and liked it, saves me so much code and I finally understood it (as much as 1 day can).
You should use self.predicate = ..... This will ensure that the proper KVO notifications are sent out, which will make your tableview update immediately (this assumes that "predicate" is a property and is bound to your array controller's filter predicate binding).

How to avoid copy and pasting?

I'd like to improve this method if possible: this is a small section whereby all of the textfield (eyepiece, objectivelenses etc) texts are saved. Unfortunately, having to do this lots of times for each part of my app is prone to error so I would like to improve it. I'm thinking some sort of fast enumeration with arguments for the method being the textfields etc. and I can have all the keys in a dictionary (which is already set up). Just a pointer to the right docs or, perhaps, some sort of process that has worked for you would be fantastic!
-(IBAction)saveUserEntries {
if (eyepiece.text != nil) {
eyepieceString = [[NSString alloc] initWithFormat:eyepiece.text];
[eyepiece setText:eyepieceString];
NSUserDefaults *eyepieceDefault = [NSUserDefaults standardUserDefaults];
[eyepieceDefault setObject:eyepieceString forKey:#"eyepieceKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"eyepieceKey"];
}
if (objectiveLenses.text != nil) {
objectiveLensString = [[NSString alloc] initWithFormat:objectiveLenses.text];
[objectiveLenses setText:objectiveLensString];
NSUserDefaults *objectiveDefault = [NSUserDefaults standardUserDefaults];
[objectiveDefault setObject:objectiveLensString forKey:#"objectiveKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"objectiveKey"];
}
Thank you for taking the time to read this!
I will attempt to answer this question based on a OOP solution.
Create a method that accepts whatever type object these textboxes are as an argument, send the reference of said object to the method, and save the entry in a similar method you do know. This will avoid the "copy and paste" errors you are worried about.
You should be able to loop through every instance of said object that exists, if a cocoa application, works like similar to Java and .NET ( I really don't know ). I just know there must be a way to loop through every instance of a single object within the application domain.
If this was .NET I simply would suggest TextBox.Name and TextBox.String to make this a generic method that could be used to save the properties of any TextBox sent to it. If this doesn't anwer your question ( was a little long for a comment ) then I aplogize.

Resources