I have encountered this error when trying to delete a cell, using a custom button.
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM removeObjectAtIndex:]: index 1 beyond bounds [0 .. 0]'
Heres how my code is structured. I have a custom UITableViewCell that contains a UIButton which has an action that deletes the cell. The action for deleting the cell is in my main View controller, that contains the actual indexPath for the cells. This structure works fine.
Heres the action to delete the cell.
NSIndexPath *indexPath = [self globalIndexPath];
[self.array removeObjectAtIndex:[indexPath row]];
[db deleteTaskAtIndex:[indexPath row]];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationFade];
When I declare the indexPath above, the globalIndexPath property is set in my cellForRowAtIndexPath, giving it the value of the original indexPath. This is how I declared it.
[self setGlobalIndexPath:indexPath];
Now I have laid some NSLogs here an there to log the indexPath. For example in the viewWillAppear method and the viewDidLoad method, and both give me the accurate indexPath, and I even checked the array outputs and all of them return accurate results, so I really don't know why it's giving me the error.
Here is the code in my custom cell, to detect when the button is tapped.
NSArray *notificationArray = [[UIApplication sharedApplication] scheduledLocalNotifications];
if (!notificationArray || !notificationArray.count)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"DeleteSignal" object:nil];
}
else
{
UILocalNotification *notif = [notificationArray objectAtIndex:[checkbox tag]];
[[UIApplication sharedApplication] cancelLocalNotification:notif];
}
And then I delete the the cell using the NSNotificationCenter with the key DeleteSignal.
If you are setting the globalIndexPath in cellForRowAtIndexPath (and nowhere else), globalIndexpath will always know the indexPath for the last cell, which got created or dequeued, not the one where the user might actually use the button. Seems already broken by design. Or are your cells that large that only one is visible?
At first I would try setting the tag of your UIButton of your cell in cellForRowAtIndexPath
myButton.tag = indexPath.row + SOMEOFFSETTOASSUREYOURTAGDOESNTGET0;
and then get the indexPath in your action via this tag:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(UIButton*)sender.tag - OFFSET inSection:0];
Of course that only works if you have just one section. If you need to handle multiple sections you could use an array which contains all your indexPaths and set the tag of your UIButton to the index of that indexPath in your array.
UPDATE
While it's technically possible to (mis)use NSNotificationCenter for what you are doing, I would really advise you to walk through this tutorial.
Related
I am trying to add a row to a tableview using
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
It throws an exception where I call insertRowsAtIndexPaths:withRowAnimation:, which says
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
It is returning the correct number of rows and sections, but it never makes it to cellForRowAtIndexPath before throwing the exception. I am doing this with a storyboard also and I have the tableView set to have static cells because the majority of the tableView does in fact have static cells. I really don't want to switch over to dynamic prototypes because then I can't have IBOutlets and IBActions, which I am using, and I would have to essentially start over with respect to the layout of the cells. Is there any way for me to get rid of this exception without changing my tableView to have dynamic prototype cells?
Edit
The line right before what I posted is
[itemsInCurrentPurchase insertObject:itemName atIndex:0];
which is supposed to update the array. I am using
[itemsInCurrentPurchase count]
for my number of rows in section and it is returning the correct number after the update. The problem is that the cellForRowAtIndexPath method is not even being reached. During the update is there anything else that gets called between the number of rows and the cell type that could be causing this error?
You need to update your data source with this new object before inserting the row. Otherwise it will crash while trying to update the table view with this new row.
for eg:-
[self.dataSourceArray insertObject:object atIndex:indexPath.row];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
I am trying to force selection(highlight) of a UITableViewCell using selectRowAtIndexPath..
For eg,
Lets say i want to always have the first cell in a table view to be highlighted,I apply the following logic in viewDidAppear
[self.tableViewPrompts selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] animated:NO scrollPosition:0];
Works nicely.But when i try to apply the same logic elsewhere in the application the cell is not selected.I even tried deselecting other cells before calling the above method and reloading tableview before and after calling this method but none of the approaches seem to work!
I even used the tableview's delegate to forcibly make a call to the didSelectRowAtIndexPath.Event this does not work.
PS: The selection of the cell does not trigger any action all i am trying to achieve is highlighting the desired cell.
What am i doing wrong here?
Help is greatly appreciated!!
The selection of the cell does not trigger any action all i am trying to achieve is highlighting the desired cell.
Why don't you use the cell's highlighted property?
UITableViewCell *cell = [self cellForRowAtIndexPath: [NSIndexPath indexPathForRow: 0 inSection: 0]];
cell.highlighted = YES;
Cant test it now, but it should work.....
edit
Better yet, of course: change your cellForRowAtIndexPath to do that
-(UITableViewCell) tableView:cellForRowAtIndexPath:
//...
if (indexPath.row == 0 && indexPath.section == 0) cell.highlighted = YES
else cell.highlighted = NO;
//...
return cell;
I'm puzzled why isn't this working in your case. Here is my method I use from several places inside my view controller including viewDidAppear and I see no real difference with what you're doing:
- (void)selectRow1
{
NSIndexPath* idx = [NSIndexPath indexPathForRow:1 inSection:0];
[_table selectRowAtIndexPath:idx animated:NO scrollPosition:UITableViewScrollPositionMiddle];
[self tableView:_table didSelectRowAtIndexPath:idx];
}
_table is my table property variable, and the call to didSelectRowAtIndexPath is made so the action of selection could happen but otherwise the only real difference with your code is the scrollPosition parameter.
Simply use setSelected to highlight cell:
[cell setSelected:YES];
I have created this extension for the whole purpose UITableView-Ext
I'm trying to properly set the target of a UIButton touched event in a subclass of UITableCell (designed in IB) that will delete that cell. However, when ran in the simulator, I get the following error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* - [__NSPlaceholderArray
initWithObjects:count:]: attempt to insert nil object from objects[0]'
It gets to the method fine, but it always crashes right after the NSArray arrayWithObject line. It seems that the cause of this is because the button passed in to the target method is always nil.
I'm assuming this is a memory issue, but I'm quite baffled as to how to fix it. Do I just need to create the cell entirely programmatically to get this to work, or is there an easy way to somehow specify the target of the buttons action as the main ViewController from Interface Builder?
Here's where the cells are created in the view controller:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView==songList){
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
SongCell *cell = (SongCell *)[tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];
if(cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SongCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
[cell.addButton addTarget:self action:#selector(addToPlaylist:) forControlEvents:UIControlEventTouchUpInside];
}
NSUInteger row = [indexPath row];
cell.songname.text = #"test";//[songListData objectAtIndex:row];
return cell;
}
return nil;
}
And here's the target method:
-(void)addToPlaylist:(id)button{
SongCell *song = (SongCell *)[(UIButton *)button superview];
NSIndexPath *indexPath = [self.songList indexPathForCell:song];
NSArray *rowToMove = [NSArray arrayWithObject:indexPath];
[self.songList beginUpdates];
[self.songList deleteRowsAtIndexPaths:rowToMove withRowAnimation:UITableViewRowAnimationFade];
[self.songList endUpdates];
}
Any help you can offer would be greatly appreciated.
The error is b/c indexPath must be nil. There are a number of suspect things in your code sample. What is your implementation of SongCell? When you add subviews to a cell you should add them to the cell's contentView vs directly to the cell. It is likley that [button superview] is not returning the cell and therefore the next line is returning nil for the indexPath.
A few other thoughts:
It looks like you only need the row number when the button is pressed. You could store the rowIndex + 1 in the button tag property and then retrieve it - 1 in addToPlaylist:. You need to +/- b/c the tag property should be > 0. Alternatively use a UIButton subclass that has a property to store your indexPath.
If you're going to delete a row via deleteRowsAtIndexPaths:withRowAnimation: you need to first delete the same row from the data source to maintain the integrity of the table.
If you are only doing a single deletion, you don't need beginUpdates/endUpdates.
I have a view controller that manages a table view. My understanding is that a table cell will be deselected automatically if I push another viewcontroller and then pop back to the table view.
However, in the same class (that I use a few times), there is one instance of the class when the cell is deselected but not animated (it'll just turn blue and then back to normal without animating). Why did this happen? I have a few instances of this class but it only happens to one of them. What might be causing this?
From my experience cells are not automatically deselected if you push/pop a view controller (at least not when using a navigationcontroller), unless you add some code te deselect it !
It may also be automatically deselected if you are doing a [tableView reloadData] in viewWill/DidAppear (or in a process started in these methods).
Did you try to add something like that in viewDidAppear ?
NSIndexPath *indexPath = [tableView indexPathForSelectedRow];
if (indexPath != nil) {
[tableView deselectRowAtIndexPath:indexPath animated:YES]
}
NSIndexPath *indexPath = [tableView indexPathForSelectedRow];
if (indexPath != nil) {
[tableView deselectRowAtIndexPath:indexPath animated:YES]
}
this you have to write in didselectRowAtIndexPath method
You can reload the Tableview again.
In your viewWillAppear
[yourTableView reloadData];
Or if you dont want to disturb your Datasource try this
NSArray *array = [yourTableView visibleCells];
for(UITableViewCell *cell in array)
{
cell.selected = NO;
}
I have a UITableView with several UITableViewcells (UITableViewCellStyleSubtitle). When the user taps on one of cells, I push a detail VC that allows the user to change the text of the cell. When I pop this ViewController, the text on the cell appears abbreviated with an ellipsis:
Before:
After:
After changing the text to "Wartyrl" and popping the detail VC.
If I tap on any other cell, the ellipsis disappears and the correct text is displayed. What might be causing this?
This is the code that changes the text of the cell (via a delegate method):
#pragma mark - FRREditTaskViewControllerDelegate
-(void) editTaskViewController:(FRREditTaskViewController *)sender
didChangeNameForTask:(FRRFlatTask *)aTask{
NSIndexPath *idx = [NSIndexPath indexPathForRow:[self.pendings indexOfObject:aTask] inSection:0];
[self.tableView cellForRowAtIndexPath:idx].textLabel.text = aTask.name;
}
BTW, this code is called before popping the detail VC and while the UITableViewController is still hidden. I don't know if this is relevant.
When the cell is created, the UILabel is only large enough to handle the initial text (it doesn't dynamically resize). Calling [self.tableView reloadData] after changing the text (end of your didChangeNameForTask method) should make it display properly.
You can call [cell.textLabel sizeToFit].
In many cases it's cleaner to instead update your internal data and call reloadRowsAtIndexPaths:withRowAnimation:. That way you don't have so many pieces monkeying with the implementation details of the cell (when you change your mind on how to lay it out). It also ensures that your real data matches the cell.
Even better in most cases is to create a UITableViewCell subclass called TaskTableViewCell. You hand it a Task and it observes the Task with KVO, managing its own layout (including calling sizeToFit when needed). That way you don't need delegate methods to tell you when it updates. When the task changes, the cell automatically updates itself. This moves all the layout issues into the cell class and out of the datasource.
It seems that the textLabel hesitates to resize its width. If its too adamant to change, just reload the tableView directly using [tableView reloadData].
If you don't want to reload the entire tableView, you can reload just a particular cell like that following.
NSIndexPath *idx = [NSIndexPath indexPathForRow:[self.pendings indexOfObject:aTask] inSection:0];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:idx] withRowAnimation:UITableViewRowAnimationFade];
This will show a nice fade animation when changing the textLabel's text of the cell.
-(void) editTaskViewController:(FRREditTaskViewController *)sender
didChangeNameForTask:(FRRFlatTask *)aTask{
NSIndexPath *idx = [NSIndexPath indexPathForRow:[self.pendings indexOfObject:aTask] inSection:0];
UITableViewCell *thiscell=[self.tableView cellForRowAtIndexPath:idx];
thiscell.textLabel.text =[nsstring stringwithFormat:#"%#",aTask.name];
[self.tableView reloadRowsAtIndexPaths: [NSArray arrayWithObject:idx] withRowAnimation:NO];//if u dont want to use reload data then use like this or use
//- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
}