Clicked Header Section is scroll to top - ios

I have the following custom headerview implementation. When user clicks on headerView and it expands to show corresponding cells.
Let's imagine I have two sections. And the first section is expanded, and second one is collapsed. When I click the second section, I want this section to scroll to the top.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
ExpandableHeaderFooterView *sectionHeaderView = [self.comboTableView dequeueReusableHeaderFooterViewWithIdentifier:SectionHeaderViewIdentifier];
if (sectionHeaderView == nil)
{
sectionHeaderView = [[ExpandableHeaderFooterView alloc] initWithReuseIdentifier:SectionHeaderViewIdentifier];
}
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(selectHeaderAction:)];
[sectionHeaderView addGestureRecognizer:tapGesture];
return sectionHeaderView;
}
-(void) selectHeaderAction :(UITapGestureRecognizer*) gestureRecognizer
{
ExpandableHeaderFooterView* cell = (ExpandableHeaderFooterView*)gestureRecognizer.view;
[self toggleSection:cell withSection: cell.section];
}
-(void)toggleSection:(ExpandableHeaderFooterView *)headerView withSection:(int)section
{
((ComboItem*)comboItemsArray[section]).expanded = !((ComboItem*)comboItemsArray[section]).expanded;
[comboTableView beginUpdates];
for (int i = 0; i< ((ComboItem*)comboItemsArray[section]).allComboItems.count; i++)
{
NSArray* rowsToReload = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:i inSection:section], nil];
[comboTableView reloadRowsAtIndexPaths:rowsToReload
withRowAnimation:UITableViewRowAnimationFade];
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:section];
[comboTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
[comboTableView endUpdates];
}

Since Fade takes about half a second to execute, you will see that flick. What you need to do is either giving a small delay to TableViewScroll, or just using:
[CATransaction begin];
[comboTableView beginUpdates];
[CATransaction setCompletionBlock: ^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:section];
[comboTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}];
for (int i = 0; i< ((ComboItem*)comboItemsArray[section]).allComboItems.count; i++)
{
NSArray* rowsToReload = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:i inSection:section], nil];
[comboTableView reloadRowsAtIndexPaths:rowsToReload
withRowAnimation:UITableViewRowAnimationFade];
}
[comboTableView endUpdates];
[CATransaction commit];

Related

App stops when click back to previous screen

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?

Add rows to the beginning of a tableview without animation

I want to add rows to a tableview with begin/endUpdates to prevent the jump of the tableview when i do reloadData
this is my code
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
[self.messagesTableView beginUpdates];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
tableViewOffset.y += heightForNewRows;
[self.messagesTableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
And sometimes (not everytime) I get this error
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 (2), 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).
How do i prevent this error ?
if you dont want animation then use directly
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[self.messagesTableView reloadData];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
If you want to quickly jump to a selected indexPath (with or without animation), you can use this function:
//My function to populate
//tableView is synthesized
-(void)setNewMessage:(NSString*)message{
// ...
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
// if your selectedIndexPath==nil, the table scroll stay in the same position
[self reloadData:selectedIndexPath animated:NO];
}
//My reload data wrapper
-(void)reloadData:(NSIndexPath *)selectedIndexPath animated:(BOOL)animated{
[tableView reloadData];
// atScrollPosition can receive different parameters (eg:UITableViewScrollPositionMiddle)
[tableView scrollToRowAtIndexPath:selectedIndexPath
atScrollPosition:UITableViewScrollPositionTop
animated:animated];
}
Once I needed that same flexibility in a tableView and this function has met my needs. You don`t need the "setAnimationsEnabled", just use:
[tableView scrollToRowAtIndexPath:nil
atScrollPosition:UITableViewScrollPositionNone
animated:NO];
];
I hope it helped.
Try this, I am not sure whether this will work or not.
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.messagesTableView beginUpdates];
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
[self.messagesTableView endUpdates];
});
}
Note: You might require to update rowsInsertIndexPath reference to use inside the block (__block type).

UITableView insert row at top crashes

I'm trying to insert a row at the top of UITableView, but it crashes in the last line where I declare the endUpdating. This is the code I'm using and I'm using a button outside the table to insert row.
[comments insertObject:comment atIndex:0];
[self.commentsTableView beginUpdates];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.commentsTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.commentsTableView endUpdates]; //here crashes with "assertion failure"
I know I can just do insert in array and reload, but I want to do it with animation.
Thank you in advance.
This is my cell for row method
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CommentCell";
// Similar to UITableViewCell, but
ListingCommentTableViewCell *cell = (ListingCommentTableViewCell *)[theTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[ListingCommentTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.commenterLabel.text = [[comments objectAtIndex:indexPath.row] objectForKey:#"user"];
[cell.commenterLabel sizeToFit];
cell.commentLabel.text = [[comments objectAtIndex:indexPath.row] objectForKey:#"text"];
[cell.commentLabel sizeToFit];
cell.lineView.frame = CGRectMake(5 , cell.commentLabel.frame.origin.y + cell.commentLabel.frame.size.height+ 5, cell.frame.size.width-20, 1);
cell.dateLabel.text = [Utils formatCommentDate:[[comments objectAtIndex:indexPath.row] objectForKey:#"createdOn"]];
return cell;
}
THis is my cell height method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
int height = [Utils findHeightForText:[[comments objectAtIndex:indexPath.row] objectForKey:#"teksti"] havingWidth:screenWidth - 40 andFont:[UIFont fontWithName:#"Ubuntu" size:14]];
height += 25;
return height + 5;
}
and my number of rows method
- (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section
{
return [comments count];
}
This is how I insert objects to UITableView with animation.
-(void)updateMethod {
[self.commentsTableView beginUpdates];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:0];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[comments insertObjects:[NSArray arrayWithObjects:comment, nil] atIndexes:indexSet];
[self.commentsTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.commentsTableView endUpdates];
}
You should check numberOfRowsInSection and be sure it reflect your comments.count
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return comments.count;
}
Also all UI updates must be done on the main thread, if you call your update method in different thread there is a change you can see that error.
You should update data source array inside animation block:
[self.commentsTableView beginUpdates];
[comments insertObject:comment atIndex:0];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.commentsTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.commentsTableView endUpdates];

Unable to remove all UITableView cells with animation

I can's seem to be able to remove all table rows. What seems to happen is that only the cells that are in view are removed and then the ones that were out of view move up. Also checked and my data source contains only the cels that are in view at the time of deletion. Heres the code
NSMutableArray *left = [[NSMutableArray alloc]init];
NSMutableArray *right = [[NSMutableArray alloc]init];
for(int i=0;i<dataSource.count;i++){
[dataSource removeObjectAtIndex:i];
NSIndexPath *ip = [NSIndexPath indexPathForRow:i inSection:0];
if(i%2==0)
[left addObject:ip];
else
[right addObject:ip];
}
[_view.tableView beginUpdates];
[_view.tableView deleteRowsAtIndexPaths:[NSArray arrayWithArray:left]
withRowAnimation:UITableViewRowAnimationLeft];
[_view.tableView deleteRowsAtIndexPaths:[NSArray arrayWithArray:right]
withRowAnimation:UITableViewRowAnimationRight];
[_view.tableView endUpdates];
What happens now is that only the cells in view are removed and the rest pop in after the removal (also the issue seems to pop up since the dataSource only contains the rows that are in view for some reason). What i want is to remove all the rows. Any ideas?
The problem is in the for loop condition. The dataSource.count gets smaller each iteration. Try this:
for(int i=0;i<dataSource.count;i++){
NSIndexPath *ip = [NSIndexPath indexPathForRow:i inSection:0];
if(i%2==0)
[left addObject:ip];
else
[right addObject:ip];
}
[dataSource removeAllObjects];
try something like this:
[_view.tableView beginUpdates];
UITableViewRowAnimation animation;
for(int i = dataSource.count - 1; i>= 0; i--)
{
[dataSource removeObjectAtIndex: i];
NSIndexPath* ip = [NSIndexPath indexPathForRow: i
inSection: 0];
if(i % 2 == 0)
{
animation = UITableViewRowAnimationLeft;
}
else
{
animation = UITableViewRowAnimationRight;
}
[_view.tableView deleteRowsAtIndexPaths: #[ip]]
withRowAnimation: animation];
}
[_view.tableView endUpdates];
You can do this in easy way...
[dataSource removeAllObjects];
[_view.tableView reloadData];
i think that you can put your dataSource = #[]; and [self.tableview reloadData]; and remove all cell in your tableview

How to delete rows from UITableView with animation?

I am having problems with deleting rows from table view. I am using the code below when the delete button in the row was pressed:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:control.tag-100 inSection:0];
[resultList removeObjectAtIndex:indexPath.row];
[resultView beginUpdates];
[resultView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[resultView endUpdates];
//[resultView reloadData];
First row was deleted successfully but then, indexes were not correct. So when I delete the last row, it gives index out of bounds exception.
The cell generation code is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"personalizeTableCell";
PersonalizeCell *cell = (PersonalizeCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[PersonalizeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.title.text = #"text";
cell.rateView.tag = indexPath.row + 100;
return cell;
}
Where am I wrong?
UPDATE:
for (NSInteger j = 0; j < [venuesTableView numberOfSections]; ++j)
{
for (NSInteger i = 0; i < [venuesTableView numberOfRowsInSection:j]; ++i)
{
PersonalizeCell* cell = (PersonalizeCell*)[venuesTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]];
cell.rateView.tag = 100 + i;
}
}
solved my problem. Thanks to Nenad M.
Assuming your [NSIndexPath indexPathForRow:control.tag-100 inSection:0]; returns the right index path....
Did you try moving the removeObjectAtIndex call inside the begin-/end-updates "bracket"?:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:control.tag-100 inSection:0];
[resultView beginUpdates];
[resultList removeObjectAtIndex:indexPath.row];
[resultView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[resultView endUpdates];
UPDATE:
Obviously your cell.rateView.tag become wrong, after youe delete your first cell.
So after every deletion (i.e. every removeObjectAtIndex...) you must re-iterate over your remaining tableview-cells an re-assign the proper tag-value (cell.rateView.tag = indexPath.row + 100)! Otherwise your [NSIndexPath indexPathForRow:control.tag-100 inSection:0]; will return a wrong indexPath, therefore leading to your out of bounds error!
Re-assigning the tag-values:
You don't have to reload the entire table, just loop through the remaining cells an re-assign the tag-value after [resultView endUpdates];:
NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger j = 0; j < [tableView numberOfSections]; ++j)
{
for (NSInteger i = 0; i < [tableView numberOfRowsInSection:j]; ++i)
{
[cells addObject:[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]];
}
}
Now do:
for (PersonalizeCell* cell in cells)
{
cell.rateView.tag = // new calculated tag
}
(Or do the re-assignment even in the inner-loop of the first code-snippet directly.)
Here's some really typical code for the whole process, two lines from the table in the example:
Note that facebookRowsExpanded is a class variable you must have:
if ( [theCommand isEqualToString:#"fbexpander"] )
{
NSLog(#"expander button......");
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NSArray *deleteIndexPaths;
NSArray *insertIndexPaths;
facebookRowsExpanded = !facebookRowsExpanded;
// you must do that BEFORE, not AFTER the animation:
if ( !facebookRowsExpanded ) // ie, it was just true, is now false
{
deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
nil];
[tableView beginUpdates];
[tableView
deleteRowsAtIndexPaths:deleteIndexPaths
withRowAnimation: UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
else
{
insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
nil];
[tableView beginUpdates];
[tableView
insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation: UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
// DO NOT do this at the end: [_superTableView reloadData];
return;
}
NOTE: your code for numberOfRowsInSection must use facebookRowsExpanded
(it will be something like "if facebookRowsExpanded return 7, else return 5")
NOTE: your code for cellForRowAtIndexPath must use facebookRowsExpanded.
(it has to return the correct row, depending on whether or not you are expanded.)

Resources