Assigning property of NSManagedObject causes UISwitch to revert - iphone

I've encountered a very odd bug. The view in question is a UITableView, where each cell contains a UISwitch as its accessoryView. I have it wired up so that a change in value triggers - (IBAction)toggleEnabled:(UISwitch *)theSwitch, which looks like this:
- (IBAction)toggleEnabled:(UISwitch *)theSwitch
{
UITableViewCell *cell = (UITableViewCell *)theSwitch.superview.superview;
NSIndexPath *indexPath = [(UITableView *)self.tableView indexPathForCell:cell];
Alarm *alarm = [self.fetchedResultsController objectAtIndexPath:indexPath];
alarm.unrelatedProperty = #"This property is unrelated to the switch.";
}
The buggy behavior is that the switch immediately reverts back to its original state. As soon as you flick it, it flicks right back. I did a little more testing, changing my code to this:
- (IBAction)toggleEnabled:(UISwitch *)theSwitch
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
Alarm *alarm = [self.fetchedResultsController objectAtIndexPath:indexPath];
alarm.unrelatedProperty = #"This property is unrelated to the switch.";
}
Now, the reverting behavior only occurs on the very first switch in the list (at row 0). All the other switches work fine, which leads me to believe that it's something to do with modifying an object at a given indexPath. The switches work fine with this code, so I'm pretty sure it's due to the assignment:
- (IBAction)toggleEnabled:(UISwitch *)theSwitch
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
Alarm *alarm = [self.fetchedResultsController objectAtIndexPath:indexPath];
}
Anyone know what could be happening? Thanks!

Related

NSInternalInconsistencyException adding more elements than expected

I have the following code
[self.tV beginUpdates];
NSIndexPath *iP = [NSIndexPath indexPathForRow:indexArray.count inSection:0];
[indexArray addObject:iP];
[self.tV insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
[self.tV endUpdates];
I get the following error
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'Invalid update: invalid number of rows in section 0. The number of rows contained in an
existing section after the update (2) 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 (2 inserted, 0 deleted) and plus or minus the number of rows moved into
or out of that section (0 moved in, 0 moved out).'
I'm not sure where the 2 inserted is coming from. This code is called every time I click a button. The first time there is one element in indexArray, as seen in the code I add one more element but it seems as if it is trying to add both elements again. Is that correct?
UPDATE
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
if(_imgList.count>0)
{
NSMutableString *fileName = [[NSMutableString alloc]
initWithString:[NSString stringWithFormat: #"img"]];
[fileName appendString: [_imgList objectAtIndex: _resultTag]];
UIImage *image = [UIImage imageNamed: fileName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(_xPos, 0, image.size.width, image.size.height);
[cell addSubview: imageView];
_xPos += SPACING;
}
return cell;
}
- (IBAction)btn:(UIButton *)sender {
[self.resultList beginUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:indexArray.count inSection:0];
[indexArray addObject:indexPath];
[self.resultList insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
[self.resultList endUpdates];
}
The issue is that you aren't changing the size of _imgList (from which, I assume, is derived the return value of the tableView:numberOfRowsInSection: method, but you don't show this method). By inserting a row in the table view, but not updating the data source to reflect that new row, you have created an inconsistency in your application.
To ensure that this doesn't happen, make the changes to your data source that reflect exactly the changes you are making to the rows, and do so before inserting or deleting any rows.
You don't show code that declares or instantiates _imgList so if it's not an NSMutableArray you'll need to make it one, and use methods like addObject:, insertObject:atIndex:, and removeObjectAtIndex: to insert and remove the appropriate objects. In your case, your code would become something like this:
NSInteger indexToAdd = _imgList.count;
[_imgList addObject:#"whatever"];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:indexToAdd
inSection:0];
[self.resultList insertRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
.h
int insertRow;
.m
- (void)viewDidLoad
{
self.insertRow = self.YourArr.count;
}
[self.tV beginUpdates];
NSIndexPath *iP = [NSIndexPath indexPathForRow:self.insertRow inSection:0];
[indexArray addObject:iP];
[self.tV insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
[self.tV endUpdates];
self.insertRow = self.insertRow + 1;
It may be solve your problem :)

Cannot get deleteRowsAtIndexPaths to work

I have a UITableView and a scheduler running in the background deleting records from the underlying data object.
Every time an object is deleted I want the table view to update accordingly.
But i keep getting this annoying error message when trying to delete a row from the table view.
* Terminating app due to uncaught exception NSInternalInconsistencyException, reason: "Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in UITableView.h if possible."
The code:
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:0];
NSUInteger row = [indexPath row];
NSMutableArray* files = [[NSMutableArray alloc] initWithArray:fileNames];
[files removeObjectAtIndex:[indexPath row]];
[fileNames dealloc];
fileNames = [NSArray arrayWithArray:files];
//[self.fileNames removeObjectAtIndex:row];
[self.tableDraft deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
PS:
I have also tried [tableView reloadData]; but that has undesired side effects, erasing the selected index.
Have you considered following the message's advice? Your index path does not contain both a row and a section component. Use +[NSIndexPath indexPathForRow:inSection:] to create the index path object, so that it has the information the table view requires:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
You need to use this:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex
inSection:sectionIndex];
Use like this
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:sender.tag-1 inSection:0];
CGRect rectOfCellInTableView = [objTableView rectForRowAtIndexPath:indexPath];
CGRect rectOfCellInSuperview = [objTableView convertRect:rectOfCellInTableView toView:[objTableView superview]];

Referencing a UITableViewCell that gets created in cellForRowAtIndexPath

I have a button on my TableView header that is an empty box, and when pressed, is a checkmark. I have that same button on the cells for that header. So I basically want to perform an action on each cell that is in that section. I'm using the Animating TableView from WWDC 2010 as my example. The header object has an array of integers that keep track of how many rows are in its section.
I'm not really sure from here, how I can get the custom UITableViewCell to perform my action on. Any thoughts? Thanks.
So far I have something like this:
- (void)selectAllCheckmarksInSectionHeaderView:(SectionHeaderView *)sectionHeaderView forSection:(NSInteger)section {
SectionInfo *sectionInfo = [self.SectionInfoArray objectAtIndex:section];
int totalRows = [[sectionInfo rowHeights] count];
for (int row = 0; row < totalRows; row++) {
NSIndexPath *path = [NSIndexPath indexPathForRow:(NSUInteger)row inSection:section];
OrderTableViewCell *cell = (OrderTableViewCell *)[_orderTableView cellForRowAtIndexPath:path];
cell.CheckmarkButton.selected = YES;
}
}
However my OrderTableViewCell is nil.
To reference a button that is a located in a section header, you'll have to assign it to an instance variable to keep a reference to it. To obtain references to all of the cells in a section and update them, you'll want to try something like this...
CGRect sectionRect = [self.tableView rectForSection:0]; // Rect for first section
NSArray *indexPathsInSection = [self.tableView indexPathsForRowsInRect:sectionRect];
for (NSIndexPath *indexPath in indexPathsInSection)
{
// Obtain the cell at each index path and update its accessory state
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
Of course this is an overly simplistic example. A more flexible approach would be to update some model object when a section is "selected" and reload the table. Then in your -tableView:cellForRowAtIndexPath: you would determine whether a cell in a section should be checkmarked based off the model object for the whole section.

UITableView didSelectRowAtIndexPath only fired sometimes

I know this is going to be one of those head-smacker moments.
I have a UITableView from which I need to delete cells. Problem is, didSelectRowAtIndexPath is only called if I first tap the row and then swipe to show the delete button. If I swipe the row without explicitly tapping the row, then I get an EXC_BAD_ACCESS error. I put an NSLog into didSelectRowAtIndexPath and can see the indexPath array when I tap the cell, but nothing if I just swipe without clicking.
I've done my share of searching and I'm 99% sure my delegate is hooked up correctly (usually problem #1) and didn't type didDeselectRowAtIndexPath (usually problem #2). I also have in my .h file (problem #3).
Thanks in advance.
EDIT:
I should have said: If I swipe the row without explicitly tapping the row, then I get an EXC_BAD_ACCESS error when I tap the Delete button. If I tap the cell and then swipe, I can tap Delete and it deletes as expected.
EDIT:
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[bandList sortUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
NSDictionary *dict = [bandList objectAtIndex:indexPath.row];
cell.textLabel.text = [dict objectForKey:#"name"];
return cell;
}
- (void)tableView: (UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath
{
NSLog(#"IndexPath: %#", [indexPath description]);
}
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
NSDictionary *selectedBand = [bandList objectAtIndex:indexPath.row];
NSString *selectedBandId = [selectedBand objectForKey:#"id"];
[self deleteMyBand:selectedBandId];
bandList = [self getSavedBands];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
These lines jump out at me:
NSDictionary *selectedBand = [bandList objectAtIndex:indexPath.row];
NSString *selectedBandId = [selectedBand objectForKey:#"id"];
I don't understand your reasoning for taking a value from an array, sticking it into a dictionary, then getting a string out of that dictionary. Shouldn't the value returned from
[bandList objectAtIndex:indexPath.row];
Be the same as what you get in selectedBandId?
Also, I can't analyze it any further when I don't know what "deleteMyBand" and "getSavedBands" do. Try commenting out those calls and seeing if the visible row itself can be deleted. That may tell you where the problem is.
Not exactly sure why, but the fact that I was using a sorted NSMutableArray for my source data seems to have been the problem. When I was deleting the row from the table and updating the MutableArray, I got the crash. However, if I created my source data as an NSArray, made a mutableCopy of the array into a temporary MutableArray, removed the row, resorted the array, and then wrote the MutableArray back out to an NSArray, everything is peachy.
I don't think this explanation makes sense, but it's the pattern I'm seeing. If anybody can think of why this might be working, please enlighten me. My gut tels me it was the sort, but my gut hasn't been correct much lately.

How can a checkmark state be saved in core data?

I have a list app where users hit the + button and enter in an item that they want to appear in the list and hit save. The table is saved with core data. The only problem is when the cell is taped I want a checkmark to be displayed. Ive enabled multiple selection with
UITableViewCell *thisCell = [tableView cellForRowAtIndexPath:indexPath];
if (thisCell.accessoryType == UITableViewCellAccessoryNone) {
thisCell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
thisCell.accessoryType = UITableViewCellAccessoryNone;
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
I would like the checkmarks to be persisted in the cell after the user exits. I have created an attribute in my entity called "checks" and gave it the type of boolean but I dont know how to make it where if you hit a row then a check appears and is persisted. Any help would be greatly appreciated. Thanks
This is how I do it. One notable point: CoreData does not store booleans, so any property labeled "boolean" is actually of type NSNumber. You've got to remember to convert back and forth when dealing with CoreData and boolean values.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *selectedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
if ([[selectedObject valueForKey:#"isDone"] boolValue]) {
[selectedObject setValue:[NSNumber numberWithBool:NO] forKey:#"isDone"];
} else {
[selectedObject setValue:[NSNumber numberWithBool:YES] forKey:#"isDone"];
}
}
I have my UITableViewController set as the the delegate for the NSFetchedResultsController, so the changes I made to the managed objects in the query ^^^ will cause the following two methods to be run.
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *defaultCellIdentifier = #"Item";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:defaultCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:defaultCellIdentifier] autorelease];
}
NSManagedObject *item = [[self fetchedResultsController] objectAtIndexPath:indexPath];
cell.textLabel.text = [item valueForKey:#"name"];
if ([[item valueForKey:#"checks"] boolValue]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
Here's how everything ties together
User clicks on a row
tableView:didSelectRow... method changes the "isDone" property of the appropriate managed object.
the fetched results controller notices that a managed object has changed and calls the controllerDidChangeContent method on its delegate.
My controllerDidChangeContent method just reloads all the data in the table view
When the tableView is reloaded, my tableView:cellForRow... method checks the "isDone" property of the managed item to see if the cell should have a checkmark or not.
And just so you don't get confused, I initially used a generic NSMangagedObject to store row state, which is why the first method I posted says, [selectedObject valueForKey:#"isDone"]. Later I switched to a subclassed managed object named JKItem, which is why the second set of methods is able to use item.isDone without generating a compiler warning.