This video shows how cells with views extending out of the cell area get clipped momentarily when new cells are being inserted:
https://dl.dropboxusercontent.com/u/22105205/CellClipping.mov
This simple project clearly shows the problem and can be used for quick prototyping:
https://github.com/AndresCanella/iOSInsertCellClippingExample.git
This clipping only occurs when the table is mutated.
Cells are clear.
Cells display correctly when not mutating.
Possibly some sort of optimization that only uses pix from within the cell area for animation?
Everything is setup correctly, stable, and works as expected, we are not even using specific cell data for this example:
[tableView beginUpdates];
self.cells++;
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];
[tableView endUpdates];
Update:
Response from DTS:
I’m afraid you are not going to be able to directly affect the insertion animation behavior when calling “insertRowsAtIndexPaths”, regardless of the kind of “UITableViewRowAnimation” you are using. Cell content conventionally don’t overlap like that. UITableView is simply honoring the cell’s bounds (not it’s extended or overlapping content), when performing its animation block of each cell.
My comment:
I've been told by DTS that things can be done plenty of times and I've always found a workaround. So now I'm looking for a work around.
Apple Bug Report # 17986466
It looks to me like the views you wish to not be clipped exceed the bounds of the cells themselves. To me, that says that these should be subviews of the table view instead of the cell.
If that is indeed the case, you may wish to use a plain UIScrollView instead of a UITableView, and animate the the views below the one being inserted downwards.
In my experience you should try to keep the cells with a non-clear/transparent background and set to clip subviews if you want to avoid weird layouts and animation glitches.
The way the cells view hierarchy is set inside the table view and how animations are made is internal to Apple implementation and prone to change without notice in future releases.
Table views are good at displaying tons of rows and reusing views, things that maybe your view is not really using. If your desired layout does not require several screens of scrollable content maybe you should try to create your own custom UIScrollView-based view or search for one among the many open source libraries. You would have complete control of animations and add custom behaviors.
I understand that this is a complete hack - but it does fix the clipping.
For granular animations - check out PRTWeen.
https://github.com/jdp-global/MWDatePicker
I guess you considered toggling 2 transparent background images (red and green) (640px x100px) on current cell and previous cell? It may work using a fade in animation on insert.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.cells++;
[self.tb reloadData];
[self performSelector:#selector(fancyAnimate:) withObject:indexPath afterDelay:0];
}
-(void)fancyAnimate:(NSIndexPath*)path{
NSIndexPath *idx = [NSIndexPath indexPathForRow:path.row inSection:path.section] ;
UITableViewCell *cell = (UITableViewCell*)[self.tb cellForRowAtIndexPath:idx];
CGRect frame = cell.frame;
frame.origin.x = 320;
cell.frame = frame;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
frame.origin.x = 0;
cell.frame = frame;
[UIView commitAnimations];
}
Related
I'm making a UIViewController to manage a messaging screen. I'm doing this using a UITableView and some custom cells.
To make things simpler, each cell contains:
Its chat "bubble" (a UIView subclass)
Its chat text (a UILabel)
A timestamp header label (which might be hidden)
A bottom footer label (for "Sending...", "Delivered", etc.; also might be hidden)
Because of performance concerns, I am not using auto-sizing of cell heights, but caching cell heights into an NSMutableDictionary.
When the user sends or receives a new message, I want the following to occur:
The current last message cell is reloaded, hiding its bottom label, if needed.
The new last message cell is appended at the bottom of the UITableView.
The UITableView is scrolled so that the new last cell is visible.
I can get it to where the end state of the screen is as desired, but the animations in between are really kinda funky. I have tried a whole lot of different approaches to get the animations to behave. Basically, it seems like some major reloading is happening, even though the only cell that could possibly change its height is the last cell (prior to the insertion of the new cell). Plus, I'd like to have the last cell simply "appear" in place without animation. If it does, it should be off-screen, and then I should be able to animate it on-screen.
Here's my current "user sent a new message" method:
- (IBAction)sendButtonPressed {
//Creation of the new message, into 'message' variable
[self.messages addObject:message];
int thisIndex = (int)self.messages.count - 1;
NSIndexPath *this = [NSIndexPath indexPathForRow:thisIndex inSection:0];
int prevIndex = (int)self.messages.count - 2;
NSIndexPath *prev = [NSIndexPath indexPathForRow:prevIndex inSection:0];
[self removeCachedHeightForIndex:prevIndex];
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[prev] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView insertRowsAtIndexPaths:#[this] withRowAnimation:UITableViewRowAnimationTop];
[CATransaction setCompletionBlock:^{
[self scrollToBottomAnimated:YES];
}];
[self.tableView endUpdates];
}
This appears to reload the last several cells in the UITableView. Or, at least, every cell that is visible when this is called seems to be animated in some way. Only the cell at prev is actually changing in any way.
Longterm, I might pull out the header and footer labels into different cells, but is there a way to fix this animation glitch as-is?
I have a custom cell which should be spaced from the edges of the display. For that I use this:
- (void)setFrame:(CGRect)frame {
frame.origin.x += kCellSidesInset;
frame.size.width -= 2 * kCellSidesInset;
[super setFrame:frame];
}
I do have a button that hides/shows the bottom view of a stacked view inside the cell. For which I use this code:
- (IBAction)showCardDetails:(id)sender {
UITableView *cellTableView = (UITableView*)[[[[sender superview] superview] superview] superview ];
[cellTableView beginUpdates];
self.details.hidden = !self.details.hidden;
[cellTableView endUpdates];
// [cellTableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationBottom];
// [cellTableView reloadData];
}
However when the table is updated to reflect the change the right padding becomes allot bigger. Then when I scroll a bit it gets fixed.
As much as I could visually judge it is like 3 times. Maybe it adds two more kCellSidesInset on the right but I doubt it.
Why is this happening and how can it be fixed? Maybe it can be avoid by instead of giving inset to the cell giving it to the UITableView (I have some trouble figuring how to do this).
PS. All the code is inside the CustomCell.m. I am open for a suggestion to a better way of getting the UITableView inside the action. Should I use selector in the CustomTableViewController.m to implement the action there when the cell is added?
EDIT: From what I can see the re rendering of the cells goes trough three phases.
Phase one, a couple of these:
Phase two, it updates the view cells:
And here everything looks good for now. The view that I want to hide/show is hidden/shown and all is good but then some sort of cleanup breaks the layout:
I solved the problem by refactoring the setFrame method to use the superview's frame of the cell as a reference point for the cell frame
- (void)setFrame:(CGRect)frame {
frame.origin.x = self.superview.frame.origin.x + kCellSidesInset;
frame.size.width = self.superview.frame.size.width - 2 * kCellSidesInset;
[super setFrame:frame];
}
Basically looking for clues/hints from anyone who may have experienced something similar. The graphical glitch/flicker occurs when I perform update a section of a UITableView. You can see this on the image below.
My refresh code looks like this:
NSInteger sectionIndex = [self getSectionListIndexForID:sectionName];
[UIView setAnimationsEnabled:NO];
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
[UIView setAnimationsEnabled:YES];
My table data, state, height, etc. is backed by dictionaries which I query for how to display the sections and cells. I basically update the dictionaries on the switch event and call the code above.
Edit
This is what it looked like if I removed the setAnimationsEnabled call.
Edit
More clues. The "frame" of the cell as it its layoutSubviews is being called during the switch animation looks like its offset by 64 pixels for one frame (the size of the nav). Slowly hunting this down...
Edit
Somewhere between the call to toggleSendAction and performWithoutAnimation, the cell's frame is offset by 64 pixels.
I have a tableview with dynamically sized cells, and a button that toggles the sort order of these cells. I'd like to scroll to the top every time the sort order is toggled, but when I set the content offset to the top, it seems to only scroll ~90% of the way there.
The offsetting code is simple enough and has served me well on different projects, so I seriously doubt the problem is here:
- (void) scrollToTop
{
CGPoint offset = CGPointMake(0, -self.tableView.contentInset.top);
[self.tableView setContentOffset:offset animated:YES];
}
[self.tableView reloadData]; // Lets update with whatever info we have
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
reloading and then scrolling resolved for me.
Didn't think I would find the answer so soon.
I was using UITableView's tableView:estimatedHeightForRowAtIndexPath: to return my minimum cell height, and it seems that the tableview uses this inside reloadData to create an idea of how big the content is before actually dequeuing the cells and caching their height. Being halfway down the content and reloading the data causes the tableview to think the distance to the top is the (number of cells offscreen above the current visible * the minimum height from estimatedHeightForRow), causing the tableview to only offset itself as if all cells were the minimum height. My solution was just to avoid using the estimated height, since my tableview isn't excessively long anyway. If you do have a large tableview (approaching 1000+ rows) that actually needs to use the estimated values for performance reasons, you might want to find a way to make the estimated values as close to the runtime values as possible, or look into more detailed solutions.
tl;dr - Remove tableView:estimatedHeightForRowAtIndexPath: and just allow the tableView to size itself from heightForRowAtIndexPath
What about something like this instead?
NSIndexPath *start = = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:start atScrollPosition:UITableViewScrollPositionTop animated:YES];
None of these worked for me. The solution was to call layoutIfNeeded() before setting the content offset:
tableView.reloadData();
tableView.layoutIfNeeded();
tableView.setContentOffset(CGPoint.Empty, animated: true);
It has been suggested that one scroll to the desired row in viewWillAppear, but this does not work with iOS 7. I have only been able to make this work in iOS 7 in the viewDidAppear callback. Unfortunately, you see the desired row scroll into view. I don't want to see any scrolling, I simply want the row to be visible when loaded. Can anyone suggest the proper way to do this in iOS 7?
It probably did not work in viewWillAppear, because that table had no data at this point.
Add [tableView reloadData];and it should work.
Let me get this straight: you want your table view to show a certain row at the top when the view apperas? Yes?
If so, you want:
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated
with your cell indexPath, UITableViewScrollPositionTop as scrollPosition and animated NO like so
[tableView scrollToRowAtIndexPath:myExampleindexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
If you know the cell index then it's as simple as:
[tableView setContentOffset:CGPointMake(cellLocation.x,cellLocation.y) animated:NO];
Call that just after you load your tableView data and it will scroll to your cell being on top. There are other options as well:
[tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:currentRow inSection:currentSection] animated:NO scrollPosition:UITableViewScrollPositionTop];
Use this code with whatever scrollPosition you would like and Apple takes care of the bounding to the table (whereas setting the scrolling position is all user defined, it could be out of the table's view).
EDIT:
You could surround your selecting code with a call to UIView setting no animations allowed. That has worked for me in the past with different things, but I have never tried it in viewDidLoad.
[UIView setAnimationsEnabled:NO];
//Scroll the tableview
[UIView setAnimationsEnabled:YES];