UPDATE: This is now fixed in iOS 8.0 and above. See my accepted answer for details.
I have an iOS 7 UITableView that I allow swipe-to-delete on rows. I'm handling deletions in:
tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
With:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationTop];
All rows are deleted with the correct animation, except for the last one in the table. When the user swipes to show the delete button, then taps it, the cell slides completely off screen to the left but leaves a white cell behind with the delete button still on it for a few tenths of a second before disappearing abruptly. It appears that this is happening with all the cells, but all other cells have a row below them that slides up, covering it up.
This even happens when the row in question is the only row in the table, where I delete the entire section instead of just the row. The section header slides up into oblivion but the white cell with the delete button sticks around for a little bit.
I would like this last cell to have the same UITableViewRowAnimationTop animation that the others do. Any ideas of what's going on?
UPDATE: This bug has been corrected in iOS 8. That final cell deletion now slides off to the left, the delete button slides up and away, and the background is clear (no more white area that abruptly disappears after the animations complete). The iOS 7 fix below is still needed when running below iOS 8.
iOS 7 Fix:
I was able to correct this problem by adding another section to the end of the table with a sufficiently tall section header view. This header view is styled to look like the blank area at the bottom of the table, so you can't see that it's there. When the last row of the table is deleted, this blank section header slides up and over it, hiding the delete button that's stuck there. This is a bit of a hack, but it looks like it's a table view bug.
As an alternative to the keep-an-empty-row trick mentioned by Frank Li, I've gotten around this ugly glitch by simply special-casing the deletion of the last row: Instead of animating the cell away, simply call -reloadData on the table view.
You won't get any animation, but you also won't see the glitch so your table view won't look broken. A worthwhile tradeoff.
my solution was to simply disable the broken animation by hiding the cell that's being deleted.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.hidden = YES;
}
I think a way-around is to always maintain an empty row below the last row in the table view. That will do the trick.
You can also use the UITableView.tableFooterView property:
- (void)viewDidLoad
{
[super viewDidLoad];
...
CGRect frame = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
UIView *view = [[UIView alloc] initWithFrame:frame];
view.backgroundColor = self.tableView.backgroundColor;
self.tableView.tableFooterView = view;
}
This code goes into the view controller holding the UITableView.
Code assumes ARC.
It's a different story if you use background image for the table.
The code can be surrounded by "if (iOS > 7)" clause.
Related
I want to be able to move/shift the group of UITableViewCells in the UITableView downward, without scrolling the tableView, and without moving the entire tableview downward. The desired effect is that there is an amount of whitespace above the table view cells after they're placed in the table view, and when the user scrolls down through the table view, the white space is covered by the cells.
Don't forget that a UITableView is a subclass of UIScrollView.
And that UIScrollView have this property.
contentInset
The distance that the content view is inset from the enclosing scroll view.
#property(nonatomic) UIEdgeInsets contentInset
Discussion
Use this property to add to the scrolling area around the content. The unit of size is points. The default value is UIEdgeInsetsZero.
I don't fully understand what you are trying to do, so I just hope that this is what you are looking for.
After Question was Edited
Add an empty cell where you want it to be and adjust it's size when needed.
You will need to make some try about doing it animated (using insert and reloadCell) or using the reloadData option of the tableView to get the visual you want.
You could use a tableView header and adjust its size. After changing its size, call:
[tableView reloadData];
If you want to animate the size change, try calling:
[tableView beginUpdates];
[tableView endUpdates];
After changing the header's size.
EDIT 1:
As opposed to a table view header, you could use a static cell as mentioned in the other answer. To animate the size change, use the steps above. Change the size in:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell *cell = [tableViewController.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
[cell setFrame:CGRectMake(cell.frame.origin.x, cell.frame.origin.y - 34, cell.frame.size.width, cell.frame.size.height)];
I want to change the frame of table view cell, the code work in some ViewController, but some it doesn't work.
I want to know is there something wrong with it.
thanks a lot.
You cannot have full control on the cell frame, especially on the origin. The table view delegate is asked for a table view cell, but then its position depends on the internal logic of the table view in order to correctly place the cell inside the table view (which is a scroll view subclass). When the displayed portion of the table view is refreshed, the cell position is rearranged and the frame origin is updated.
So while your code is not wrong, it conflicts with some table internal rules you cannot fully control and so the results could unpredictable.
If you want instead to see a visible effect of this change, you can move your frame changer code to another part of the code, e.g. inside the tableView:didSelectRowAtIndexPath:
In such case you can for example touch a cell, call this code and change the cell frame and you will the change immediately. But as soon as you scroll the cell out of the iPhone screen, and then you move back this cell in the screen, you will notice that it will re-appear in its original position.
I know that you can animate insertions of table view cells and section but sometimes I need to change the footer text to one with a different text or to one that is 'nil'. I need to animate it when a UISwitch is toggled.
Right now I am using [tableView reloadData] but this is ugly and changes the section footer too sudden and without any animations. Apple somehow animates this for example when you turn on Personal Hotspot the section footer is slightly changed and this change is animated.
How can I achieve the same effect?
Assuming you've already written the code in -tableView:titleForFooterInSection: to adjust in response to the UISwitch, you could just send an empty update block to the table view to get it reload with animation.
[self.tableView beginUpdates];
[self.tableView endUpdates];
I'm experiencing this strange situation. I have a UITableView where, when the user selects a cell, a long (network) process begins. So, I performed this in a background thread and I placed (in the didSelectRowAtIndexPath) a UIActivityIndicatorView as the accessory view. This is what I wrote:
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView* activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
cell.accessoryView = activityView;
[activityView startAnimating];
[activityView release];
everything seems to work correctly, except that, if during a loading process (when the indicator is animated), I switch the view using a UITabBar, when I go back to the UITableView, the UIActivityIndicatorView that should still be there has disappeared. Any idea of what I did wrong? Thanks!
I was looking back at this issue, and... after reading the question now, I'm laughing :-)
Answer to my own question is: nothing is wrong with the code, it works very well... but making the UIActivityIndicatorView white... makes it difficult to see it on white background :-D :-D The posted code is correct.
When you go back to the table view after going to another tab, cellForRowAtIndexPath: messages are sent to the table view controller to display the cells of the table but the activity indicator view was set as the accessory view of the cell in the didSelectRowAtIndexPath: method. So, essentially, you changed the cell (i.e., displayed the activity indicator view) when the cell was selected but when you left the view and came back the cellForRowAtIndexPath: method was used to re-display the cell (and, hence, no activity indicator view).
You'll have to keep track of what cells currently have activity indicator views and make sure you set the accessory view of those cells with a UIActivityIndicatorView in the cellForRowAtIndexPath: method. Obviously, if the activity associated with a cell has completed then don't display the activity indicator view for that cell so you'll have to keep track of whether the activities have completed yet or not. There are many ways you can do this so you'll have to decide what works best for your situation.
It's not true that cellForRowAtIndexPath: messages are sent (at least necessarily) when returning to the table view from another tab view so I've "deleted" my answer above.
I have an iphone app using a uitableview where I'd like the "reorder" controls to always be displayed, and have the user swipe to delete rows.
The approach I'm currently taking is to put the tableview in edit mode and allow selection in edit mode
self.tableView.editing = YES;
self.tableView.allowsSelectionDuringEditing = YES;
I then hide the red delete circles using
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleNone;
}
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
I cant figure out how to get the swipe gesture to bring up the "delete" on the right side when the tableview is already in edit mode, can somebody point me in the right direction?
alternatively, if someone knows how to get the cell reordering controls to show when NOT in edit mode, that would also be a workable solution
cheers
When the user swipes on a given row, you need to store a reference somewhere so that you can change the value returned by editingStyleForRowAtIndexPath and shouldIndentWhileEditingRowAtIndexPath. Your best bet is likely to use indexPathForCell on the cell that is swiped and store that. Then in the two display methods above you need to check if the NSIndexPath is the same or not (I'm not sure if they will be the same pointer or if you'll need to compare the section/row values - testing required). If they match, display the delete button.
Note that you may need to call reloadData on your tableView to have the effect appear without scrolling away and back again.
I'm wondering if the way you're headed now would break the Human Interface guidelines, which would result in the app not getting approved.
I can't see why you can't capture the swipe gesture and then use that to 'unhide' the red delete (stop sign) icons for the delete confirmation?