I'm currently working on an implementation of an inline UIDate picker inside of a UITableViewCell.
I'm able to show and hide this picker cell when I select the cell directly above where that cell should be inserted, which is the behavior that I expect. However, the app crashes if I select any other cells in the table view:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.16.14/UITableView.m:1582
After looking at the accepted answer to this SO question, I added an exception breakpoint, and I've found out that the app is crashing at the call to [tableView endUpdates]; in didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
// Check to see if the "Only Alert From" row was selected. The cell with the picker should be below this one.
if (indexPath.section == TimeOfDaySection && indexPath.row == HourTimeZoneRow && self.timePickerIsShowing == NO){
[tableView beginUpdates];
[self showTimePicker];
[tableView endUpdates];
} else{
[tableView beginUpdates];
[self hideTimePicker];
[tableView endUpdates];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
That said, I'm not sure how to proceed. If I comment out the call to [tableView endUpdates];, the app won't crash when other cells are selected, BUT the cell with the picker view won't hide. Does anyone have any suggestions? Thank you!
EDIT: Below is my code for showTimePicker and hideTimePicker:
- (void)showTimePicker
{
self.timePickerIsShowing = YES;
self.timePicker.hidden = NO;
//Create the index path where we insert the cell with the picker
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:HourTimeZoneRow + 1 inSection:TimeOfDaySection];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
self.timePicker.alpha = 0.0f;
[UIView animateWithDuration:0.25 animations:^{
self.timePicker.alpha = 1.0f;
//This is the row where the picker cell should be inserted
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:HourTimeZoneRow + 1 inSection:TimeOfDaySection]] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
}];
}
- (void)hideTimePicker {
self.timePickerIsShowing = NO;
self.timePicker.hidden = YES;
//Create the index path where we delete the cell with the picker
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:HourTimeZoneRow + 1 inSection:TimeOfDaySection];
[self.tableView beginUpdates];
//Delete the picker row
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
[UIView animateWithDuration:0.25
animations:^{
self.timePicker.alpha = 0.0f;
}
completion:^(BOOL finished){
self.timePicker.hidden = YES;
}];
}
EDIT 2: After reading this SO thread, I believe the problem may be with my numberOfRowsInSection method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch (section) {
case NotificationsSection:
return TotalPreferencesRows;
break;
case RedZoneSection:
return TotalRedZoneRows;
break;
case TimeOfDaySection:
if (self.timePickerIsShowing) {
return TotalTimeOfDayRows + 1;
}
// else if (self.timePickerIsShowing == NO){
// return TotalTimeOfDayRows;
// }
else{
return TotalTimeOfDayRows;
}
return TotalTimeOfDayRows;
break;
default:
return 0;
break;
}
}
Not sure if this is the source of the crash, but it's not recommended to call beginUpdates multiple times which you're doing in
[tableView beginUpdates];
[self showTimePicker];
[tableView endUpdates];
because showTimePicker calls [self.tableView beginUpdates];
The problem is in your didSelectRowAtIndexPath, you call the hide method even though it might not be showing. Make the else clause into an else if, like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
// Check to see if the "Only Alert From" row was selected. The cell with the picker should be below this one.
if (indexPath.section == TimeOfDaySection && indexPath.row == HourTimeZoneRow && self.timePickerIsShowing == NO){
[tableView beginUpdates];
[self showTimePicker];
[tableView endUpdates];
} else if (indexPath.section == TimeOfDaySection && indexPath.row == HourTimeZoneRow && self.timePickerIsShowing == YES){
[tableView beginUpdates];
[self hideTimePicker];
[tableView endUpdates];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Related
I would like to create cell by expand and collapse. To do this i animate the cell by reloading. When i expand it works fine. But when i collapse it crashes and the reason is Attempt to create two animations for cell. I know Ive given same array paths and it wont reload two cell at the time. Is there a way to fix this.
I am new to coding, so i would be happy to get a simple solution.
Code:
The reason am using lastSelIPath is when first cell is expanded and touch the second cell the first cell would collapse and the second cell would expand.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath* lastSelIPath = [ NSIndexPath indexPathForRow:selectedIndex_ inSection:0 ];
[ tableView deselectRowAtIndexPath:indexPath animated:YES ];
if (selectedIndex_ == indexPath.row)
{
selectedIndex_ = -1;
}
else
{
selectedIndex_ = indexPath.row;
}
NSIndexPath* ipath = [ NSIndexPath indexPathForRow:indexPath.row inSection:0 ];
NSArray* indexPathArr = [ NSArray arrayWithObjects:lastSelIPath, ipath, nil ];
[ tableView beginUpdates ];
[ tableView reloadRowsAtIndexPaths:indexPathArr withRowAnimation:UITableViewRowAnimationAutomatic ];
[ tableView endUpdates ];
}
So what i did now I set a condition and reload the one i wanted. Is it a correct method to follow please help.
Modified code:
if ( lastSelIPath.row == ipath.row )
{
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else
{
[tableView reloadRowsAtIndexPaths:#[lastSelIPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
Crash is right, you are trying to attempt two animation. Your indexPathArr contains two objects. Do not create this array and try this:
[tableView reloadRowsAtIndexPaths:#[lastSelIPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:#[ipath] withRowAnimation:UITableViewRowAnimationAutomatic];
Let me know, if it helps
[tableView reloadRowsAtIndexPaths:#[newIndexPath, oldIndexPath] withRowAnimation:UITableViewRowAnimationNone];
If newIndexPath is same as oldIndexPath, it will crash in iOS8, but this is fixed in iOS9 and later.
You can use third party library for expanding cell.
https://github.com/bennyguitar/CollapseClick
& implement there delegate method as
-(int)numberOfCellsForCollapseClick
{
}
-(NSString *)titleForCollapseClickAtIndex:(int)index {
}
-(UIView *)viewForCollapseClickContentViewAtIndex:(int)index {
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (selectedTag==1) {
selectedIndex=indexPath.row;
[self performSelector:#selector(Write Your Action) withObject:nil];
}
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandedIndexPath]) {
[modifiedRows addObject:self.expandedIndexPath];
self.expandedIndexPath = nil;
}
else {
if (self.expandedIndexPath)
[modifiedRows addObject:self.expandedIndexPath];
self.expandedIndexPath = indexPath;
[modifiedRows addObject:indexPath];
}
// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Using Selected Tag write the condition of your cell expand action. may be it helps.
Here is my code. And yesterday it works just fine. But this morning, it doesn't work.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
RSDrug *drug = [[RSDrug alloc]init];
[drugArray insertObject:drug atIndex:0];
NSIndexPath *ip = [NSIndexPath indexPathForRow:1 inSection:0];
[tableView insertRowsAtIndexPaths:#[ip] withRowAnimation:UITableViewRowAnimationAutomatic];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return drugArray.count + 1;
}
I have tried to add some code like beginUpdates/endUpdates, or I delete insertRowsAtIndexPaths, just use reloadData.
But same result. When the app runs to insertRowsAtIndexPaths or reloadData, it stuck, no response.
So I need some help. How to solve this?
Found the problem..........A stupid mistake
There are two UITextFields in the added cell, and they all have the same leftView, my code is like this:
UIImageView *leftImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"img"]];
field_1.leftView = leftImage;
field_2.leftView = leftImage;
I never know that I can't reuse the leftView. If I set the leftImage to field1, I can't set it to field2 anymore.
When I changed to this, it works again.
field_1.leftView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"img"]];
field_2.leftView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"img"]];
Try this sequence...
NSMutableArray *indexPaths=[[NSMutableArray alloc]init];
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[tableView beginUpdates];
RSDrug *drug = [[RSDrug alloc]init];
[drugArray insertObject:drug atIndex:0];
if (indexPath.row == 0) {
[indexPaths removeAllObjects];
[indexPaths addObject:[NSIndexPath indexPathForRow:indexPath.row++ inSection:0]]; // here inserting row index=1;
[tableViewMenuList insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}
[tableView endUpdates];
}
Have you tried this?
RSDrug *drug = [[RSDrug alloc]init];
[drugArray insertObject:drug atIndex:0];
[tableView beginUpdates];
// indexpath inserted is with the index 0 same with the `insertObject: `
NSIndexPath *ip = [NSIndexPath indexPathForRow:0 inSection:0];
[tableView insertRowsAtIndexPaths:#[ip] withRowAnimation:UITableViewRowAnimationAutomatic];
//here is what i use to do instead of #[]
//[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
EDIT
Your code are working fine. i tested it. here..
Code:
Output:
Log:
Check the tested code above if you have omitted something.
I think the problem is the delegates of your table, make sure that your table have the delegate for both UITableViewDataSource & UITableViewDelegate.
Hope this is helpful.. Cheers.. :)
I'm filling a tableview with data from a server. I fill it well, but I have a function to display the details of each row in another screen, but when I push back, the app Crash, but I don't know why!!
This is my function:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 1) {
NSUInteger i, totalNumberOfItems = [gestiones count];
NSUInteger newNumberOfItemsToDisplay = MIN(totalNumberOfItems, numberOfItemsToDisplay + kNumberOfItemsToAdd);
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for (i=numberOfItemsToDisplay; i<newNumberOfItemsToDisplay; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
numberOfItemsToDisplay = newNumberOfItemsToDisplay;
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
if (numberOfItemsToDisplay == totalNumberOfItems) {
[tableView deleteSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationTop];
}
[tableView endUpdates];
// Scroll the cell to the top of the table
NSIndexPath *scrollPointIndexPath;
if (newNumberOfItemsToDisplay < totalNumberOfItems) {
scrollPointIndexPath = indexPath;
} else {
scrollPointIndexPath = [NSIndexPath indexPathForRow:totalNumberOfItems-1 inSection:0];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 200000000), dispatch_get_main_queue(), ^(void){
[tableView scrollToRowAtIndexPath:scrollPointIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
});
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}else{
Gestion *ges = [gestiones objectAtIndex:indexPath.row];
DetalleGestionViewController *vcc = [[DetalleGestionViewController alloc] initWithNibName:#"DetalleGestionViewController" bundle:nil];
vcc.gestion = ges;
[self.navigationController pushViewController:vcc animated:YES];
NUEVA_GESTION = YES;
[gestiones removeAllObjects];
}
}
With "NUEVA_GESTION" I prepare the app to reload the table data.
Can someone help me?
I have a static tableview that was designed using storyboard. Whenever I select one cell and call reloadSections:withRowAnimation: it causes the two cells above it to disappear but displays the 4 cells that it should. Anybody know why this is happening?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.section == 1) {
if (indexPath.row == 0) {
}
else if (indexPath.row == 1) { // Map Type Cell
self.isSelectingMapType = ![self isSelectingMapType];
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
else {
// Configure the single selection
if (self.checkedIndexPath) {
UITableViewCell *uncheckCell = [tableView cellForRowAtIndexPath:self.checkedIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
// Check the cell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// Store the new indexPath
self.checkedIndexPath = indexPath;
// Save the map type indexPath
[LBSettings saveObject:indexPath forKey:kLBSettingsMapTypeIndexPath];
// Save the map type
if (indexPath.row == 2) {
// Save the map type standard
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeNormal] forKey:kLBSettingsMapType];
}
if (indexPath.row == 3) {
// Save the map type satellite
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeSatellite] forKey:kLBSettingsMapType];
}
if (indexPath.row == 4) {
// Save the map type hybrid
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeHybrid] forKey:kLBSettingsMapType];
}
if (indexPath.row == 5) {
// Save the map type terrian
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeTerrain] forKey:kLBSettingsMapType];
}
self.isSelectingMapType = ![self isSelectingMapType];
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in each section
switch (section) {
case 0:
return 3;
break;
case 1:
if (self.isSelectingMapType == YES) {
return 6;
}
return 2;
break;
case 2:
return 2;
break;
case 3:
return 6;
break;
case 4:
return 0;
break;
default:
break;
}
return 0;
}
Try to call reloadData after the animation transaction is finished.
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self.tableView reloadData];
}];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[CATransaction commit];
The matter is, I want to have a grouped table view, and when I click on a cell, I want to have an another cell to appear under it, and so on. Everything is fine with workflow, but I have same problems with animations. It works very slowly and I can see all changes
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"selected section:%d selected row:%d",indexPath.section,indexPath.row);
NSIndexPath* pathToDelete;
[_tableView beginUpdates];
if (_selectedPath != nil && [_selectedPath isEqual:indexPath]){
//If the same row was selected
pathToDelete = [NSIndexPath indexPathForRow:_selectedPath.row+1 inSection:_selectedPath.section];
_selectedPath = nil;
[_tableView beginUpdates];
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:pathToDelete] withRowAnimation:UITableViewRowAnimationTop];
[_tableView endUpdates];
} else {
//If not
if (_selectedPath != indexPath){
//delete the row that we selected last time
if (_selectedPath != nil){
pathToDelete = [NSIndexPath indexPathForRow:_selectedPath.row inSection:_selectedPath.section];
[_tableView beginUpdates];
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:pathToDelete] withRowAnimation:UITableViewRowAnimationTop];
[_tableView endUpdates];
}
//add new row in the section we selected
_selectedPath = indexPath;
[_tableView beginUpdates];
[_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[_tableView endUpdates];
}
}
[_tableView beginUpdates];
[_tableView deselectRowAtIndexPath:indexPath animated:NO];
[_tableView endUpdates];
[_tableView reloadSections:[[NSIndexSet alloc] initWithIndex:pathToDelete.section] withRowAnimation:UITableViewRowAnimationMiddle];
[_tableView reloadSections:[[NSIndexSet alloc] initWithIndex:_selectedPath.section] withRowAnimation:UITableViewRowAnimationMiddle];
[_tableView endUpdates];
}
I think the problem is with begin & endUpdates method, but I don't know where exactly. Any clue?
Thanks
You could do this much simpler, by using a single beginUpdates - endUpdates. You just make your deletions and insertions inside of it. Don`t forget to update the data source for the table, otherwise the app will crash.
Hope this helps!