NSArrayController : removeAllObjects does not refresh TableView - objective-c

In my application I add objects directly to an ArrayController. When I want to clean all items I do:
[[downloadItemsController content] removeAllObjects];
This command however does not refresh the TableView the arraycontroller is bound to. If I remove all items and add another new items I only see that item. That is fine but if I don't add anything I still have all my items in the table.
If I do
[downloadItemsController prepareContent];
all old items are removed from the tableview but than I will get an new and empty item I can edit. I don't want that and because one of my columns has a checkboxcell I always get an row with a checkbox.
I just need an empty table with no items after I remove all existing items.

To quickly remove all the objects from an NSArrayController object:
NSRange range = NSMakeRange(0, [[anArrayController arrangedObjects] count]);
[anArrayController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:range]];
The bindings should update automatically. See original SO answer.

This is because you're modifying the controller's content "behind its back." Try using the array controller's -removeObjects: method.

This worked for me
[anArrayController setContent:nil]
You can start populating its contents right away like this
[anArrayController addObject: #{ .... }]
[anArrayController addObject: #{ .... }.mutablecopy]
[anArrayController addObject: aDictionary]

Calling removeObject.. group of methods is particularly inconvenient for a collection of Core Data objects. It seems that remove... is meant for those cases when we really need to get rid of the data (like when user press 'Remove' button at UI), so the NSArrayController will try it's best to remove objects from Core Data DB too.
I have simple manual control of NSArrayController and I found that setContent: method works great for adding, removing and replacing the array of objects inside it. And just to clear the contents setContents:#[] can be used.

Sometimes when you remove is the key; here I defer using a volatile bool (undoInProgress) in a revert action, using solution above to flush a change array which observing method ignores while undo is in progress:
volatile bool undoInProgress = 0;
- (IBAction)revert:(id)sender
{
NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults];
NSArray * undo = [NSArray arrayWithArray:changes];
undoInProgress = YES;
// We have a single key/value pair but do so in reverse order
for (NSInteger i=[undo count]-1; i>=0; --i)
{
NSDictionary * change = [undo objectAtIndex:i];
NSString * key = [change objectForKey:#"keyPath"];
id val = [change objectForKey:NSKeyValueChangeOldKey];
[prefs setValue:val forKey:key];
}
dispatch_async(dispatch_get_main_queue(), ^{
NSRange range = NSMakeRange(0, [[changeController arrangedObjects] count]);
[changeController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:range]];
undoInProgress = NO;
});
}

Related

NSMutableArray Resetting Itself?

I am having an issue with NSMutableArray wiping its contents.
Consider my code: (int i; is in my file's .h as is NSMutableArray* newFileControllerArray)
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [[NSMutableArray alloc]init];
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"%lu",(unsigned long)elementsInArray);
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"THERE ARE %lu ELEMENTS IN THE ARRAY",(unsigned long)elementsInArray);
}
The first method called (other than awakeFromNib:) is newFileMenubar: This will add one element to the array. I can confirm that this works because 1 is printed in the console. However, once OKbutton is called and I print out the number of elements in my array it says that no elements are in the array. Why is that?
Am I missing something very obvious here? Why does my array reset itself?
EDIT:
The comments have gotten long and unwieldy so here is the code w/NSLogs and outputs:
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [NSMutableArray array];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
i++;
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
[[newFileWindowControllerArray objectAtIndex:i]close];
}
When the program launches, this is the output: self=0x100141480, array=0x100140f30
This should be coming from awakeFromNib:
The next method called is newFileMenubar:
The output from this is
self=0x1001ac990, array=0x1005228a0 and immediately after self=0x100141480, array=0x100140f30
The last method called is OKButtonClicked:
The output from the last method (OKButtonClicked:) is self=0x1001ac990, array=0x1005228a0
As you can see from the code, the name of the array doesn't change, but my outputs beg to differ? What could cause this?
There are good clues in your log output. There are multiple instances of the view controller (see the different values for 'self'?). They each have their own array. See this code...
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
When you press the button associated with that action, your app builds another view controller and places it in the array. That view controller gets the awake from nib message and allocates another array, and so on.
To confirm this, change the code as follows:
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:#"Hello world"];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
In the other methods, comment out your expectations that the array has anything other than strings in it, and see what you get. e.g. ...
- (IBAction)OKButtonClicked:(id)sender {
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i]close];
// instead...
NSLog(#"danh thinks my array will be ok: %#", newFileWindowControllerArray);
}
You probably do not mean to create another view controller on every button press, but I'm not sure what function you do want. Maybe you want an array of views? (To create many view controllers under the control of another, you'll want to read up on container view controllers, here).

Fastest way to find an NSManagedObject in an NSSet

I have 2 NSSets with NSManagedObjects, the objects for each set are fetched in different threads, meaning some have a matching objectID, but the objects themselves are different. Now I want to remove managedObjects in one set from the other.
NSSet* oldObjects;
NSMutableSet* currentObjects;
// I want to remove the managedObjects in oldObjects from currentObjects, all objects in oldObjects are also in currentObjects
// This doesn't work, since the objects don't match
[currentObjects removeObjectsInArray:[oldObjects allObjects]];
// But strangely enough, this doesn't add any objects to currentObjects, but if the objects don't match, shouldn't it?
//[currentObjects addObjectsFromArray:[oldObjects allObjects]];
// This does work for me but this code is running on the main thread and I can see this becoming rather slow for large data sets
NSArray* oldObjectIDs = [[oldObjects allObjects] valueForKey:#"objectID"];
[currentObjects filterUsingPredicate:[NSPredicate predicateWithFormat:#"NOT (objectID IN %#)", oldObjectIDs]];
Is there a faster way I can filter these out? Would fast enumeration be faster even in this case?
Sorry for getting back to with such a delay.
I re-read your question, and now, that I've understood the setting completly I might have a solution for you.
This is not tested, but try something like this:
//Since the current objects set has registered its objects in the current context
//lets use that registration to see which of them is contained in the old object set
NSMutableSet* oldRegisteredSet = [NSMutableSet new];
for (NSManagedObject* o in oldObjects) {
NSManagedObject* regObject = [context objectRegisteredForID:[o objectID]];
if (regObject) {
//You could do here instead: [currentObjects removeObject:regObject];
//You should optimize here after testing performance
[oldRegisteredSet addObject:regObject];
}
}
[currentObjects minusSet:oldRegisteredSet];

arraycontroller nsmutablearray add programmatically

I trying to add a data on table view via Array Controller thats bind to a NSMutableArray.
On the IB property it looks like this :
and on the code I tried to add the NSMutableArray dynamically then reload the view, bu nothings happened.
for(int i=0;i<10;i++){
NSMutableDictionary *group = [[NSMutableDictionary alloc]init];
[group setValue:[NSString stringWithFormat:#"%#-%d", #"Group", i] forKey:#"groupname"];
[contentArray addObject:group];
}
[tableContent reloadData];
I have been google it and browse the same question in stackoverflow, not found a useful one.
any idea ?
Thanks
updated
I wrote above code in File's owner class.
I think the problem is that the array needs to send a KVO notification to the array controller (or maybe it's the table view, I'm not sure). The way to do that is:
self.contentArray = contentArray; (or _contentArray if that's what your ivar is called). I'm assuming that contentArray is a property, if not, you should make it one.

Outlets not working after showing "old" view again

I have several UIViews in my Storyboard and, of course, I can switch between them using a segue. Initially this works just fine:
notenKurse is a NSMutableArray, and kurse1Outlets is an outlet collection with my UITextFields.
int counter = 0;
for (UITextField *tf in kurse1Outlets) {
NSMutableString *t = [NSMutableString stringWithFormat:#"%#", [notenKurse objectAtIndex:counter]];
NSLog(#"Object at index %i is %#", counter, [notenKurse objectAtIndex:counter]);
if ([t isEqualToString:#"42"]) {
[t setString:#""];
}
[tf setText:t];
NSLog(#"UITextField in slot %i should now display %#", counter, t);
counter++;
}
All of my UITextFields are displaying the value stored in the array. But if I go to another view (let's assume I have a Button for it ;) ) Change something, and then go back to the original UIView the above code still gets executed, and there are different values in the array (this is supposed to be). I can see that in the log. But the stupid UITextField just doesn't display anything. Neither what was in there before, nor the new text. But why? The log clearly shows that t is what it's supposed to be, so the error must be in writing it into the textfield, and therefore I guess it's an outlet issue...
There is no guarantee of the order of your outlet collection. It's treated very much like an NSDictionary as opposed to an NSArray - where order is guaranteed. Iterating over this sort of collection will yield different results for different devices/people/phase of the moon.
When I use a collection like this I tend to set the 'tag' and then reorder the outlet collection when viewDidLoad by sorting off of the tag.
self.calendarDayImageViews = [_calendarDayImageViews sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if ([(UIView *)obj1 tag] < [(UIView *)obj2 tag]) {
return NSOrderedAscending;
}
else if([(UIView *)obj1 tag] > [(UIView *)obj2 tag]){
return NSOrderedDescending;
}
else{
return NSOrderedSame;
}
}];
You can just output the tf,by
NSLog(#"%#",tf);
To check if the tf is null
Ok, i found it. I forgot to release some stuff, and so my UITextFields did get set before the array was sorted. My mistake!

why the tableview doesn't show binding data?

Here's my code of generating data
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[array initWithCapacity:20];
}
- (IBAction) readlog:(id)sender {
for (int i = 0; i < 20; i++) {
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingFormat:#"/%d.log",i]];
[array addObject:d];
}
}
- (IBAction) writelog:(id)sender {
for (int i = 0; i < 20; i++) {
NSMutableDictionary *d = [NSMutableDictionary dictionary];
NSString *name = [NSString stringWithFormat:#"testfile%d", i];
[d setObject:[NSDate date] forKey:#"date"];
[d setObject:[path stringByAppendingFormat:#"/%d.log", i] forKey:#"path"];
[d setObject:name forKey:#"name"];
[d writeToFile:[path stringByAppendingFormat:#"/%d.log", i] atomically:YES];
}
and I bind my tableview column with appdelegate.array with keypath name/path/date
but it doesn't show any data in the array.. is there anything wrong here?
Thanks!
You haven't created an array.
init methods, including NSMutableArray's initWithCapacity:, initialize an existing (freshly-created) instance. You haven't created one, so you're sending that initWithCapacity: message to nil, which means it has no effect.
You need to create the array, then initialize it, then assign it to your array variable, preferably all in the same line.
There's also the issue that your table view will have already asked for the array by the time you receive the applicationDidFinishLaunching: message. You don't have one yet, so it gets nothing; by the time you create one, it has already asked you for it and gotten its answer, and does not know that it should ask again.
Create your array in init or initWithCoder: (I believe you will need the latter if your app delegate is in a nib), and implement and use Key-Value-Coding-compatible accessor methods to fill the array with values. When you send yourself accessor messages, you'll cause KVO notifications that will tip off the table view that it needs to ask for the array again. Assigning directly to the instance variable will not cause this effect.
A couple of other thingsā€¦
You have three [path stringByAppendingFormat:#"/%d.log", i] expressions in two different methods. Don't repeat yourself. Move that to a method named something like logFileNameWithLogFileNumber: and send yourself that message to generate the filename. This will make the code both clearer and easier to maintain.
Finally, as a matter of style, you should not use stringByAppendingFormat: or stringWithFormat: to construct paths. Use stringByAppendingPathComponent: (in this case, together with stringWithFormat: to generate the filename). Clarity and pathname-separator-independence are virtues.