Update predicate on arraycontroller - cocoa

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

Related

What issues could arise when using GCD dispatchAfter() in this use case

I'm going through a book on OS X programing as a refresher and have a document app set up with an array controller, tableView etc. The chapter calls for implementing undo support by hand using NSInvocation. In the chapter, they call for adding a create employee method and manually, adding outlets to the NSArrayController, and connecting my add button to the new method instead of the array controller.
Instead I did this with my method for inserting new objects:
-(void)insertObject:(Person *)object inEmployeesAtIndex:(NSUInteger)index {
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]removeObjectFromEmployeesAtIndex:index];
if (![undoManager isUndoing]) {
[undoManager setActionName:#"Add Person"];
}
[self startObservingPerson:object];
[[self employees]insertObject:object atIndex:index];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Wait then start editing
[[self tableView]editColumn:0 row:index withEvent:nil select:YES];
});
}
This works ok (looks a bit silly), but I was wondering the what issues could arise from this. I've done this elsewhere in order to execute code after an animation finished (couldn't figure out a better way).
Thanks in advance.
Why are you delaying the invocation of -editColumn:row:withEvent:select:?
Anyway, the risks are that something else will be done between the end of this -insertObject:... method and when the dispatched task executes. Perhaps something that will change the contents of the table view such that index no longer refers to the just-added employee.

NSArrayController's fetchPredicate not working as expected

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.

Won't save changes to an EKEvent that has recurrenceRules

The following code works for non-recurring events, changes to startDate and endDate are saved just fine.
BOOL success = [theEventStore saveEvent:event
span:EKSpanFutureEvents
commit:YES error:&error];
But whenever I try to edit an event that has recurranceRules, it returns with success == YES, but nothing is saved, and any changes to startDate/endDate or the recurranceRules are reverted back to the original values. (Using span:EKSpanThisEvent works, but this is of course not what I want to do. Also, the code works on iOS, but not on OSX.)
eventWithIdentifier returns the first occurrence of a recurring event. When you change something on this event with EKSpanFutureEvents, you will change all occurrences.
eventsMatchingPredicate returns every event occurrence that matches your predicate. EKSpanFutureEvents will change each occurrence from the certain occurrence you used.
If a event is detached it doesn't matter, if you take EKSpanThisEvent or EKSpanFutureEvents.
I don't get what your code is supposed to do.
I think I've found a solution, or at least a workaround. It seems that on Mac OS X when you modify recurrent events you should get them with eventWithIdentifier and not use the ones from eventsMatchingPredicate.
NSArray* events = [_eventStore eventsMatchingPredicate:predicate];
EKEvent* event = [events objectAtIndex:index];
EKEvent* original = [_eventStore eventWithIdentifier:event.eventIdentifier];
if (event.isDetached)
{
… // modify detached event
success = [_eventStore saveEvent:event
span:EKSpanThisEvent
commit:YES
error:&error];
}
else if (!original.hasRecurrenceRules)
{
… // modify non-recurrent event
success = [_eventStore saveEvent:event
span:EKSpanFutureEvents
commit:YES
error:&error];
}
else
{
… // modify the original in a series of recurring events
success = [_eventStore saveEvent:original
span:EKSpanFutureEvents
commit:YES
error:&error];
}
I haven't found any good documentation on this, maybe it's a "bug" or just one of those peculiar behaviors of EventKit. In any case it seems that you need to take great care when modifying recurring events.

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.

How can I get the values I set for custom properties in AddressBook to persist?

I have created a custom property in AddressBook named "Qref". I can check it's there using [ABPerson properties], and it's always there for any test app I write.
By the way, you can't remove custom properties, because [ABPerson removeProperties] hasn't been implemented. Let me know if it ever is, because I need to remove one whose name I mistyped.
I set a property value with this code.
ABPerson *p = <person chosen from a PeoplePicker>;
NSError *e;
if (![p setValue: aString forProperty:#"Qref" error:&e]) {
[NSAlert alertWithError:e]runModal;
}
(I have never seen the alert yet, but sometimes get a heap of error messages in the console.)
At this point I can navigate away from the person in the PeoplePicker and return to find the value correctly set.
If I check [[ABAddressBook sharedAddressBook] hasUnsavedChanges] the result is NO, so clearly changing a custom property value doesn't count as a change, so I force a save by inserting dummy person (please suggest a better way), then executing
[[ABAddressBook sharedAddressBook] save];
The dummy person appears immediately in AddressBook if it is running, so something's right. But when I close my app and run it again, I find the values I set have gone.
(MacOSX-Lion)
I've been barking up the wrong trees. It's turning out that I couldn't save any properties, irrespective of whether they were custom ones or not. Then I wondered if it was something to do with code-signing, entitlements or iCloud, which is impenetrable jungle to me.
It seems the person you get from a PeoplePicker isn't associated with any address book so [[ABAddressBook sharedAddressBook] save] will do nothing. You have to get your person from an ABAddressBook instance. Here's the skeleton of what works, without any error checking.
ABPerson *p = [[myPeoplePicker selectedRecords] objectAtIndex:0];
NSString *uid = p.uniqueId;
ABPerson *editablePerson =
(ABPerson*) [[ABAddressBook sharedAddressBook] recordForUniqueId:uid];
// Typecast because it returns an ABRecord. Can anyone improve?
[editablePerson setValue:#"ABCD" forProperty:#"Qref"]; // my custom property
[[ABAddressBook sharedAddressBook] save];

Resources