I have a tableview where I want to reload (and change the height of) just the sectionHeader but not the cells inside this section. The reason I want to do this is because I use the fade animation on the header, like so:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationFade];
But at the same time I am adding a cell in that section with
[self.tableView insertRowsAtIndexPaths:
#[[NSIndexPath indexPathForRow:0 inSection:0]]
withRowAnimation:UITableViewRowAnimationRight];
I'd like to be able to keep the UITableViewRowAnimationRight for the cell that is being added, but when I reload the Section, the fade animation gets applied to the whole section. Is there any way to just reload the sectionHeader, and not the cells in the section?
Just create 2 separate brackets of
[tableView beginUpdates];
[tableView endUpdates];
for the two insertions / changes.
I have my search display view controller based off TableSearch.
Normally, following expression gives selected index row:
self.tableView.indexPathForSelectedRow.row
However while in search mode, selected row can be obtained using:
self.searchDisplayController.searchResultsTableView.indexPathForSelectedRow.row
I use the above indices to remove respective rows (from my back end arrays) soon after some processing - at the end of didSelectRowAtIndexPath. The arrays I use are listContent and filteredListContent - as per the Apple example.
My issue is, while in search mode, I remove a row using:
[self.filteredListContent removeObjectAtIndex: self.searchDisplayController.searchResultsTableView.indexPathForSelectedRow.row]
However, at the same time, I also want to remove the same object from self.listContent because when I return to non-search mode, that row should not appear.
I saw that self.tableView.indexPathForSelectedRow.row does not update regularly while in search results view. Instead, it gives the last selected row before I entered the search results view.
Off course, I can put some content into my array objects so that both indexes can be cross-referenced. But is there any efficient solution other than that? I think table view should have this mechanism.
And yes, I already do following in my viewWillAppear:
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
This is working fine for me...
#pragma mark - delete row
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
if(tableView == self.searchDisplayController.searchResultsTableView)
// searching mode
{
// find row (index) from listContent
arrayObject = [filteredlistContent objectAtIndex:indexPath.row];
NSUInteger fooIndex = [listContent indexOfObject: arrayObject];
NSLog(#"index listContent %u", fooIndex);
//remove from listContent
[self.listContent removeObjectAtIndex:fooIndex];
[self.tableView reloadData];
//remove from filteredlistContent
[self.filteredlistContent removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else // tableView mode
{
[self.listContent removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
}
I'm writing an iPhone app with a UITableView as the primary user interface. Each section consists of two rows, a header and the body. When the user clicks on the header, I remove the second row by changing the numberOfRowsInSection value:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
cbwComponent *comp = [_componentController objectInListAtIndex:section];
if([comp hasContentsView] && !comp.contentsHidden){
return 2;
}else
return 1;
}
When the user selects the header, I'm using the following code:
comp.contentsHidden = YES;
[self.tableView beginUpdates];
NSArray *deleteIndexPaths = [[NSArray alloc] initWithObjects:[NSIndexPath indexPathForRow:1 inSection:indexPath.section], nil];
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
It's working great, with a nice smooth fade effect. The problem is, I'm trying to add an indicator in the header cell (row 0) that changes when it's clicked on. To change that image I have to refresh the top row as well as the second row, which makes the transition look bad (well, not nearly as smooth). Is there a way to change the image in a UITableViewCell without refreshing the cell?
Thanks
EDIT: I figured it out! You can maintain the smooth transition as long as you reload that first row before you make the change to the second row. It has to be called inside of [tableView beginUpdates];
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[[NSArray alloc] initWithObjects:[NSIndexPath indexPathForRow:0 inSection:indexPath.section], nil] withRowAnimation:UITableViewRowAnimationNone];
...
[self.tableView endUpdates];
Did the trick.
You could also subclass a tableview cell and implement a view transition in it that can be called from your view controller. You could then call that without having to reload the cell.
[(YourCustomCell*)[tableView cellForRowAtIndexPath:indexPathOfYourCell] fadeInIndicator];
I am interested on how tweetbot does the following:
I would like to create the same thing with my app, where if you click on a row, it pops
an additional UIToolBar and pressing on any other row will dismiss this view with animations.
The logic I think is simple, you just need to add a subView to the UITableViewCell when pressed and shift the rest of the content up, but how do you actually dismiss it when I press the other row?
The best way to do this is to add a dummy cell below the cell that was tapped.
First you need to keep track of what cell is been tapped and act accordingly.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//if user tapped the same row twice let's start getting rid of the control cell
if([indexPath isEqual:self.tappedIndexPath]){
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
//update the indexpath if needed... I explain this below
indexPath = [self modelIndexPathforIndexPath:indexPath];
//pointer to delete the control cell
NSIndexPath *indexPathToDelete = self.controlRowIndexPath;
//if in fact I tapped the same row twice lets clear our tapping trackers
if([indexPath isEqual:self.tappedIndexPath]){
self.tappedIndexPath = nil;
self.controlRowIndexPath = nil;
}
//otherwise let's update them appropriately
else{
self.tappedIndexPath = indexPath; //the row the user just tapped.
//Now I set the location of where I need to add the dummy cell
self.controlRowIndexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
}
//all logic is done, lets start updating the table
[tableView beginUpdates];
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationNone];
}
//lets add the new control cell in the right place
if(self.controlRowIndexPath){
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:self.controlRowIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
//and we are done...
[tableView endUpdates];
}
Whenever you have that dummy cell present you have to make sure to send the correct count.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(self.controlRowIndexPath){
return modelArray.count + 1;
}
return self.modelArray.count;
}
Also, return the appropriate height for your ControlCell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath isEqual:self.controlRowIndexPath]){
return 45; //height for control cell
}
return 70; //height for every other cell
}
Lastly, remember the control cell is a dummy. Is not part of the model, thus you have to account for that. If the user taps a row that is above the last tapped row is ok but when the new tapped row is below that control cell you have to make sure you access the right row in your model. In other words, account for that fake cell in the middle of your view.
- (NSIndexPath *)modelIndexPathforIndexPath:(NSIndexPath *)indexPath
{
int whereIsTheControlRow = self.controlRowIndexPath.row;
if(self.controlRowIndexPath != nil && indexPath.row > whereIsTheControlRow)
return [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
return indexPath;
}
I hope this helps.
In tableView:didSelectRowAtIndexPath:, you remove the tool view from the last selected cell. If there is no such view, you create a new one. Then you add this view to the newly selected cell. Save the indexPath of the selected row.
In tableView:heightForRowAtIndexPath:, you check if the indexPath is the same as the saved indexPath. If they are equal, you return a height that is the height of both views. If it is not equal, just return the height of the "real cell".
Put all your calls in didSelectRowAtIndexPath between [tableView beginUpdates] and [tableView endUpdates] to get animation for the height change.
rjgonzo's code works fine except for the case where you only have 1 row in the tableview. When there's only 1 row (and 1 object in the tableview datamodel) you'll get an NSRange exception when you call insertRowsatIndexPath(s). To fix this I checked to see if the datamodel has only 1 object and if so then I add the row to the current indexpath (instead of the controlindexpath) which results in the row logically being added above the first row and then I call moveRowAtIndexPath to interchange the rows after calling [self.tableView endUpdates]. The animation shows as expected with the control row appearing to slide down from the 1st row.
if(self.controlRowIndexPath){
//Check to see if the datamodel only has 1 object
if([self.objects count]==1){
//If so then insert the row above the row
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
else
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:self.controlRowIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
[self.tableView endUpdates];
//if the row was inserted above the 1st row then switch the rows
if([self.objects count]==1)
[self.tableView moveRowAtIndexPath:self.controlRowIndexPath toIndexPath:indexPath];
I would not add a subview to a UITableViewCell, I would add another row to the UITableView. That way, the UITableView will take care of the animation. (And I don't think that's possible to animated UITableViewCell height changes...)
Use simply
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
do add a row. And
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
to remove it.
#rjgonzo this works great but there is a minor issue on how you keep the indexPathToDelete. Since it's just another pointer to self.controlRowIndexPath, once you clear or reassign the self.controlRowIndexPath, indexPathToDelete will not be what you wanted, and tableView deleteRowsAtIndexPaths:withRowAnimation: call, you will get an SIGBART crash.
so, instead of
//pointer to delete the control cell
NSIndexPath *indexPathToDelete = self.controlRowIndexPath;
and
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationNone];
}
the following code should work fine:
//pointer to delete the control cell
NSIndexPath *indexPathToDelete = [NSIndexPath indexPathForRow:self.control_row_index_path.row inSection:self.control_row_index_path.section];
...
...
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete.row != 0){
NSLog(#"row to delete %d", indexPathToDelete.row);
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete] withRowAnimation:UITableViewRowAnimationNone];
}
Here's what I'm doing to get the animation to be clean.
In addition to the strategy user fluchtpunkt suggests (that is, adding a subview to the cell, updating the cell height via heightForRowAtIndexPath, beginUpdates, and endUpdates), I'm finding the following measures to be helpful with the animation:
The tableviewcells have a background image. Otherwise, the added subview/toolbar is visible through the cell just before the tableview animates the height change.
The tableviewcell is 'behind' the view that is the cell below it, otherwise again the subview/toolbar will show too soon. I'm using [tableview sendSubviewToBack:cell]; and it's taking care of that.
This is clean for me, but not exactly like Tweetbot. Interestingly, Tweetbot's animation seems to pull the toolbar down as the bottom of the cell animates down. It seems like some additional animation must be taking place, or my conspiracy theory is that it is actually adding the subview to the top of the cell below the selected cell, and then performing the lengthening and animation on the cell below.
I am devleoping a shopping list with all the unbought items to be on the top of the UITable and all the bought items to be on the bottom. The items are read from sqlite DB When the user first clicks on a shopping list item, it means the user has bought the item. Hence i change the label to grey color and move the cell to the bottom,correspondingly in the data source as well. (i have done this by deleting the item and inserting)
However when the user clicks on it again (meaning they need to buy this again), i want the cell to move to the top. How do i do that? How do i update the datasource ?
Here is my code
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Item *item=[[appDelegate.slist objectAtIndex:indexPath.row]retain];
NSUInteger index=indexPath.row;
NSUInteger lastindex=appDelegate.slist.count-1;
//get the cell
UITableViewCell *cell =[tableView cellForRowAtIndexPath:indexPath];
if (item.iNeed==1) {
cell.textLabel.textColor=[UIColor lightGrayColor];
if (index!=lastindex) {
[appDelegate deleteSLItem:item]; //delete from DB
item.iNeed=0;
[appDelegate insertSLItem:item]; //insert again
//for animation
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:lastindex inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
}
}else if (item.iNeed==0) {
item.iNeed=1;
cell.textLabel.textColor=[UIColor blackColor];
/*
i want to know what am i supposed to do here to move the cell to the top and
correspondingly in the data source as well
*/
[appDelegate updateSLItem:item];
}
[item release];
}
You shouldn't delete and add the item.
Add a bool attribute "bought" to the model.
When displaying the cells make sure to sort your array starting with the ones with bought = YES;
When you click a cell change the bought=!bought
Do reloadData on the table
EDIT: if you want to have the bought ones in the correct order, instead of a bool make "bought" an int and store it's order index