Reputation: 53
My app has a UITableView containing variable-height cells with transparent backgrounds. When building against the iOS 7 SDK there's a problem with the row deletion animation:
Note the flickering of some cells, e.g. rows 15, 17, 21 and 23. This behavior was not observed on iOS <= 6.
If I do any of these things the problem goes away (none of which I'm yet prepared to do):
Nothing else I've tried helps. Is there some secret sauce required on iOS 7 to get smooth row deletion animations?
Here's the smallest test case I can distill the problem to [C#/Xamarin]:
public class MainViewController : UITableViewController {
public MainViewController() {
this.TableView.Source = new TableSource(this.TableView, 50);
}
public override void ViewDidAppear(bool animated) {
base.ViewDidAppear(animated);
NSTimer.CreateRepeatingScheduledTimer(0.5f, ((TableSource) this.TableView.Source).DeleteFirstRow);
}
}
public class TableSource : UITableViewSource {
private UITableView tableView;
private List<RowInfo> activeRows;
public TableSource(UITableView tableView, int numRows) {
this.tableView = tableView;
this.activeRows = new List<RowInfo>();
for (int i=1; i<=numRows; i++) {
this.activeRows.Add(new RowInfo(i));
}
}
public void DeleteFirstRow() {
if (this.activeRows.Count == 0) return;
this.activeRows.RemoveAt(0);
this.tableView.BeginUpdates();
this.tableView.DeleteRows(new NSIndexPath[] { NSIndexPath.FromRowSection(0, 0) }, UITableViewRowAnimation.Right);
this.tableView.EndUpdates();
}
public override int RowsInSection(UITableView tableview, int section) {
return this.activeRows.Count;
}
public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath) {
return this.activeRows[indexPath.Row].Height;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) {
const string CellID = "Cell";
UITableViewCell cell = tableView.DequeueReusableCell(CellID);
if (cell == null) {
cell = new UITableViewCell(UITableViewCellStyle.Default, CellID);
}
cell.TextLabel.Text = this.activeRows[indexPath.Row].Text;
return cell;
}
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath) {
cell.BackgroundColor = UIColor.Clear;
}
private class RowInfo {
public RowInfo(int index) {
Random random = new Random(index);
this.Height = random.Next(20, 80);
this.Text = "Row " + index + " has height " + this.Height;
}
public float Height;
public string Text;
}
}
Upvotes: 2
Views: 1240
Reputation: 2318
TLDR: cell.clipsToBounds = YES;
====
Here's what I found:
' | | | | <UITableViewCell: 0x79e81910; frame = (0 232; 320 32); text = 'row 27'; clipsToBounds = YES; autoresize = W; animations = { position=<CABasicAnimation: 0x79e8f020>; }; layer = <CALayer: 0x79e81aa0>>
| | | | | <UITableViewCellScrollView: 0x79e81ad0; frame = (0 0; 320 32); autoresize = W+H; gestureRecognizers = <NSArray: 0x79e81d60>; layer = <CALayer: 0x79e81ca0>; contentOffset: {0, 0}>
| | | | | | <UIView: 0x79e8b7d0; frame = (0 0; 320 51); clipsToBounds = YES; layer = <CALayer: 0x79e8b830>>
| | | | | | <UITableViewCellContentView: 0x79e81f50; frame = (0 0; 320 31.5); gestureRecognizers = <NSArray: 0x79e82160>; layer = <CALayer: 0x79e81fc0>>
| | | | | | | <UILabel: 0x79e821b0; frame = (15 0; 290 31.5); text = 'row 27'; userInteractionEnabled = NO; layer = <CALayer: 0x79e82260>>
| | | | | | <_UITableViewCellSeparatorView: 0x79e824d0; frame = (15 31.5; 305 0.5); layer = <CALayer: 0x79e82540>>
If you look closely at row 27, you'll notice that the cell's height is 32px. The subviews of the cell is a scrollView (I've read somewhere that's the scroll view that you swipe left to show the row actions in iOS7). The contentView, as we see, has the correct height of 32px, but, and here's where the funny part begins: there's a view just behind the contentView, that has a height of 51 px. I believe that's the culprit.
Unfortunately, I wasn't able to find which view is that (it's not the backgroundView, it's not the selectedBackgroundView, etc., in short: it's not any of the views in the public properties of the UITableViewCell class).
Also, on the wierd side of things, that view's backgroundColor is clearColor. In theory it shouldn't even be visible! What we're dealing here is, I believe, some CoreAnimation optimization that doesn't render the area behind this mystery view.
So, what happens is: 1) the cell is created 2) cell is resized and prepared to be presented 3) cell gets its mystery view added of the correct size 4) cell gets reused 5) cell is resized to the new dynamic height 6) scrollView & contentView get resized as well, but the mystery view isn't (please note it doesn't have any resize mask set).
This explains why you're able to fix this issue in any of the three described ways.
The workaround is to set the cell's cell.clipsToBounds = YES;
upon creation.
I would also recommend against setting the cell.backgroundColor. IIRC, you should use the cell.backgroundView for that (set it to a newly created view and set it's backgroundColor instead). However the cell's default bacgroundColor is clear anyway, so this doesn't really matter in this context.
I would be glad to hear from someone what's this private view for.
Upvotes: 3