NSFetchedResultsControllerDelegate and deleteRowsAtIndexPaths: Section issue - objective-c

In response of a delete operation on a CELL (not a section) of a tableView connected to a NSFetchedResultsController i obtain this error :
'Invalid update: invalid number of
sections. The number of sections
contained in the table view after the
update (1) must be equal to the number
of sections contained in the table
view before the update (2), plus or
minus the number of sections inserted
or deleted (0 inserted, 0 deleted).'
I understand that the problem is related to Section numbers after and before update. It says that i didn't delete sections, but the after-value is different from the before-value.
Ok this's true! but my section depend on row cells, so if i remove the last row cell of a section, the section disappear.
Here how i define Section and Row numbers:
Sections are created to group row by an attribute "date". Thus, if a row has the attribute "date" 10 April 2010 and a second row has 11 April 2010 i have 2 sections containing 1 row.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.controller sections]count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
And here my definition of commitEditingStyle, where i remove cell from table and delete data from DB (object type "Transactions" is a subclass of NSManagedObjectContext, that define my model).
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
Transactions *trans = (Transactions *)[self.controller objectAtIndexPath:indexPath];
//Delete transaction
[self.context deleteObject:trans];
NSError *error = nil;
[self.context save:&error];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
}

I find where's the problem.
The delegate is defined in a controller called from a UITabBar. Every time its view appears, a function is called to Fetch data on NSFEtchedResultController(with performFetch) and setup the delegate for the NSFetchedResultControllerDelegate. This way (i don't know exactly why) every time i try to delete or insert cell/section on table, the delegate seems to be duplicated as many time as i opened the controller via UITabBar, thus Delegate function are repeated more than one time creating problem and errors that i described in the question.
To avoid this problem, now i setup Request and Delegate in init function of ViewController and in no other places. This ensure that it set only one reference to delegate and only one Fetch is performed over NSFetchResultController.

Related

Reordering UITableViewCells with Core Data TableView

I have a UITableView that is working with Core data for the user o add their own Cells... but i wanted them to be able to reorder the TableViewCells the way they wanted... i use the code now BUT whenever the reorder it, say i went to add another cell, it would return the the regular state... images below if you're lost...
Below is the code:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) // Don't move the first row
return NO;
return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
id ob = [_devices objectAtIndex:destinationIndexPath.row];
[_devices replaceObjectAtIndex:destinationIndexPath.row withObject:[_devices objectAtIndex:sourceIndexPath.row]];
[_devices replaceObjectAtIndex:sourceIndexPath.row withObject:ob];
}
You should add an order property on your entity so that your sort order is persistent.
Then your data source for the table view needs to be sorted by that order key.
It is likely that your _devices array is getting reinitialized with the results of your Core Data fetch request and therefore your modification to the order is lost.
Updated for comment
Assuming you have an entity named Device that is a sub-class of NSManagedObject with a property named order you could do the following.
NSSortDescriptor *orderSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
[devicesFetchRequest setSortDescriptors:#[orderSortDescriptor]];
Then when you execute your fetch request and capture the results in your _devices array they will be sorted by the order property.
Then in - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath you will need update the order property on the affected entities.
If you move the device from position 5 to position 2 then you need to update the entities from position 2-4 by +1 and the moved entity to 2. This could get inefficient for large data sets quickly but for small data sets it should perform fine.

Populate NSTableView with unknown number of columns

I have a NSTableview and a button. NSTableview has a unknown number of columns.
The first column has a image well and 2 text boxes, the others (again, unknown number) are normal textbox columns.
The button opens up a file open dialogue. Once I choose the files (.jpg) I would like to process them.
So far everything is made (chose files, columns, etc..) what is missing is the populating of the table:
I have the loop that goes through all the selected files. What is the best way to do this:
display the image in the image well of the first cell,
type the filename in the first textbox of the first cell,
type the filepath in the second cell of the textbox,
type "YES" in all other columns.
My difficulty is that I have no idea how many columns will be there since it depends from the user. The number of columns will not change during Runtime. they are set up at startup based on the configuration. if the configuration is changed then the app should be reloaded.
I am a beginner in Objective-C/Cocoa programming.
EDIT:
additional info as requested:
It is a view based NSTableView
each column represents an action that has to be taken in a later moment on an image. the program user can decide what and how many actions to take, thats the reason for a unknown amount of columns in the table view.
You can add columns programmatically using addTableColumn:. This method takes an NSTableColumn instance that you can create in your code.
The rest of your architecture (displaying images, etc.) does not particularly change from "normal" code just because the columns have been added dynamically.
Here is a snippet that should get you started:
NSTableColumn* tc = [[NSTableColumn alloc] init];
NSString *columnIdentifier = #"NewCol"; // Make a distinct one for each column
NSString *columnHeader = #"New Column"; // Or whatever you want to show the user
[[tc headerCell ] setStringValue: columnHeader];
tc.identifier = columnIdentifier;
// You may need this one, too, to get it to show.
self.dataTableview.headerView.needsDisplay = YES;
When populating the table, and assuming that the model is an array (in self.model) of NSDictionary objects, it could go something like this;
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *columnIdentifier = tableColumn.identifier;
NSDictionary *rowDict = [self.model objectAtIndex: row];
NSString *value = [rowDict valueForKey: columnIdentifier]; // Presuming the value is stored as a string
// Show the value in the view
}
More in the docs.
When user adds a column or row, you should reflect it in your model (by binding or by code), so you know the size of your table, when you need to populating it.
set tableView.delegate (in code or in Interface Builder), reference here
implement:
- (NSView*) tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row
{
Item* itemView = [tableView makeViewWithIdentifier:#"rowItem" owner:self];
/*Here you populate your cell view*/
id entryObject = [self.entries objectAtIndex:row];
[itemView setEntry:entryObject];
return itemView;
}
and then invoke [tableView reloadData];
maybe for you better to use this method
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
Just see the NSTableViewDataSource and NSTableViewDelegate

inserting UITableViewCell not working

Could somebody explain what I'm doing wrong in attempting to insert a new UITableViewCell? I'm trying to insert a custom UITableViewCell, but it throws the following error: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (section == 0)
return 1;
else if (section == 1)
return numberOfRows;
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"EditableCell";
EditableCell *editableCell = (EditableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (editableCell == nil) {
editableCell = [[EditableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
_myTextField = [editableCell textFieldCell];
if (indexPath.section == 0){
[_myTextField setPlaceholder:#"Menu Name"];
[_myTextField setReturnKeyType:UIReturnKeyNext];
}
else if (indexPath.section == 1){
[_myTextField setPlaceholder:#"Category"];
[_myTextField setReturnKeyType:UIReturnKeyNext];
}
else {
[_myTextField setPlaceholder:#"Category"];
[_myTextField setReturnKeyType:UIReturnKeyDone];
}
_myTextField.keyboardType = UIKeyboardTypeDefault;
_myTextField.delegate = self;
return editableCell;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
if (textField.returnKeyType == UIReturnKeyNext) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
NSArray *array = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView insertRowsAtIndexPaths:array withRowAnimation:UITableViewRowAnimationTop];
numberOfRows = numberOfRows + 1;
[self.tableView reloadData];
}
}
You need to make sure that the number of rows returned in the method below now returns the correct number of rows.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
I am sure what is happening is that you are returning the same value here after your insert, whereas the runtime is expecting it to be one more than it was before you made the call to insert a row.
In a bit more detail, what is happening in MVC terms (Model/View/Controller) is this:
Your call to insertRowsAtIndexPaths is requesting that the Controller update the View with an additional row.
The Controller then does a sanity check to see if you have your sums right, so it calls:
tableView:numberOfRowsInSection
Now the controller knows what value this method returned last time (in your case, 1), and it also knows that you have requested to insert a row. There have been no delete calls, so it expects the next time it calls this method, the value should be (last time's value + rows inserted - rows deleted) = 2
If the Controller deems that you have things in order, it will then call cellForRowAtIndexPath (along with other methods) to get the actual cells for each of the rows in each section.
So for your problem, you need to keep track of the rows in your model - maybe in an array or an ivar with the count of rows available. Update your model by updaing this value as you add/delete rows before you make the call to insertRowsAtIndexPath, and then return this value when tableView:numberOfRowsInSection is called.
Additional tip:
If you wish to have your cell inserts/deletes to be animated, change your code to the below. Notice there is no call to reloadData anymore, instead the insert/reload is wrapped in begin/end update calls - The animated updates will occur after endUpdates due to the reloadSections call.
NSMutableIndexSet *sectionsToReload = [[NSMutableIndexSet alloc] init];
[sectionsToReload addIndex:1]; // Add sections as necessary
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:array withRowAnimation:UITableViewRowAnimationTop];
[self.tableView reloadSections:sectionsToReload withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
Check out the WWDC 2010 video from Apple, "Mastering Table Views" - explains everything really nicely, regarding using insertRowsAtIndexPaths and associated methods.
HTH,
Stretch
After you attempt to insert a new cell, your datasource must contain the number of original cells (1) plus the "extra" cell you are inserting (1) for a total of 2. So your tableView:numberOfRowsInSection: must return 2.
The error message indicated that tableView:numberOfRowsInSection: returned 1.
Update your datasource so that it returns this "extra" cell.

Delete object from Array and UITableView

I have a NSMutableArray with a list of prizes.
With those, I filled a UITableView.
Now, I´m trying to delete one of those prizes using TableView CommitEditingStyle. And giving me the "Invalid update: invalid number of sections...." error.
I readed a lot of post over internet about the "Invalid update: invalid number of sections...." but I can´t make it work.
First I remove the object from the array and then update the TableView (like all the posts teach me). But still give me the error
I try setting the array to nill and is the same.
Is necesary to refresh the array or something like this?
This is my Code:
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[[self myPrizesList] removeObjectAtIndex: [indexPath row]];
[self.tableView beginUpdates];
// Either delete some rows within a section (leaving at least one) or the entire section.
if ([[self myPrizesList] count] > 0)
{
// Section is not yet empty, so delete only the current row.
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
else
{
// Section is now completely empty, so delete the entire section.
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
}
I always said that, for a 5 element array, "the number of sections contained in the table view after the update (5) must be equal to the number of esctions contained in the table view before the update (5), plus or minus the number of sections inserted or deleted (0 insert, 1 deleted)"
If I set the array to nil, say: "the number of sections contained in the table view after the update (1) must be equal to the number of esctions contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 insert, 1 deleted)"
Thanks and sorry for my poor english
It is the sections deletion that is the issue, you are deleting the final prize AND section, but when the table reloads it still has the original number of sections... 5. How are you determining the number of sections to return? This needs to reduce if you have deleted one.

Obtain a stringValue from NSTableView

I have a simple NSTableView which I have loaded with data from a NSMutableArray. When I select a row (entry) in the tableView and modify it, I can find the row index, what I cannot get is the edited entry out as a string so I can modify the array. I can find lots of information on selecting rows, etc., but not on how to get the actual modified string. I keep thinking this should be real simple. Help please.
Part of my code:
- (IBAction)toDoEdit:(id)sender // Accept the edited data
{
NSString *toDoItem = [[toDoTableCell:toDoTableView dataCellFoTableColumn:0 row:rowToBeEdited] stringValue];
// I get the error "dataCellForTableColumn' method cannot be found.
[toDoArray replaceObjectAtIndex:rowToBeDeleted withObject:toDoItem];
[toDoTableView reloadData];
[toDoTableView deselectRow:rowToBeDeleted];
}
~~~~~~~~~~~
// This method should return the cell value of the selected row
- toDoTableCell:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return toDoTableCell; // No errors here but toDoTableCell is nil.
}
The 'Add' data to tableView works, 'Delete' data from tableView works, I just cannot get the edited data out of tableView so I can reload the data with the corrections.
What you are looking for is an NSTableView Delegate method:
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
This will return the NSCell that is in the row and column that you specify. From the NSCell you should be able to extract the value that you need. Depending on how you are using your NSCell you would either call [cell stringValue] or [cell objectValue].
Try this:
– tableView:setObjectValue:forTableColumn:row:
in - NSTableViewDataSource Protocol Reference
--- Edited based on comment ---
Above method is called whenever user tries to edit a table row, it also provides user with changed value as parameter. If you are trying to edit the row in table itself then it should serve your purpose. You can simply check the objectValue obtained as parameter and verify if it is correct or not. In case it is incorrect you can modify the obtained value and set it in todoArray.
Briefly:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
// below case is an example, you can add your own
if([anObject isEqualToString:#"Incorrect"])
{
anObject = #"Correct";
}
// Considering todoArray is array of dictionary items containing keys as table-column identifiers
NSMutableDictionary *originalData = [todoArray objectAtIndex:rowIndex];
[originalData setValue:anObject forKey:[aTableColumn identifier]];
[toDoTableView reloadData];
}
To get the value being edited you can simply use this code in above method, before setting the new value:
NSString *editedValue = [[todoArray objectAtIndex:rowIndex] valueForKey:[aTableColumn identifier]];
Hope this helps :)
It is simple. Read up on Cocoa Bindings and NSArrayController.
Take a look at the NSTableView methods selectedColumn, selectedColumnIndexes, selectedRow and selectedRowIndexes. I guess they should provide you with the needed information.
Now you can query the model, i.e. the array, for the data you need.