Graphical glitch on UITableView when reloading a section - ios

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.

Related

Maintain current position of UITableView while calling reloadData with variable height custom cells of UITableView

I have UITableView with variable height custom cells and multiple sections which are not fixed, i am trying to implement load more functionality while user reach at first cell.
After fetching data i am arranging records into NSMutableArray which contains multi-dimensional array to store data section vice.
My problem is when i load more data i don't have idea about how many sections and how many rows in each section comes. So i can not add fix values to move my UITableView at particular position using methods like scrollToRowAtIndexPath or scrollRectToVisible
So every time after getting new record i called reloadData to update my number Of Sections and number Of Rows In Each Section, which also move control to first row of UITableView. I want to be present at current viewing cell not at first cell.
I have also tried answers at reloadData() of UITableView with Dynamic cell heights causes jumpy scrolling this question but that are not helping me.
Don't use reloadData if you want to stay at the same position. Use reloadRowsAtIndexPaths or insertRowsAtIndexPaths or reloadSections instead.
To refresh modified rows with animation:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPathOfYourModifiedCell] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
To add rows with animation (number of rows is automatically increased):
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPathOfYourNewCell] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
Without animation (untested):
[UIView performWithoutAnimation:^{
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPathOfYourNewCell] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}];
Apple documentation: description here

insertRowsAtIndexPaths: doesn't animate without reloadRowsAtIndexPaths:

I've created a set of classes to simplify managing table views. One of the things they do is create a diff for a section based on what data is updated and then apply the correct insertions, deletions and moves.
This is all working great for most of my app, except for two uncooperative table views. Call them table A and table B. I've found a fix that works for each individually, but I can't fix both at the same time. I've checked that the diffs are being correctly generated, but for some reason I get different animations in each.
Both table A and table B are simply inserting rows. There are no deletions or moves.
In the below code BREAK_TABLE_A causes:
Table A - All rows in section=0 row>=1 (not row=0) to shift to the bottom while re-animating in from the top. Very ugly
Table B - A nice insertion animation
BREAK_TABLE_B causes:
Table A - A nice insertion animation
Table B - No animation. Just like reloadData
Ultimately, I would actually prefer the method in BREAK_TABLE_B because this allows me to keep the same cells for the same data, which means I can perform animations on those individual cells using configureCell:.
The code is relatively straightforward:
UITableViewRowAnimation animation = UITableViewRowAnimationFade;
...
[self.tableView beginUpdates];
NSMutableSet *insertedPaths...;
NSMutableSet *deletedPaths...;
NSMutableSet *refreshPaths...; // Will contain any cell that was present before and after the update.
[self.tableView moveRowsAtIndexPaths:... // Not executed in these examples
...
[self.tableView insertRowsAtIndexPaths:insertedPaths.allObjects withRowAnimation:animation];
[self.tableView deleteRowsAtIndexPaths:deletedPaths.allObjects withRowAnimation:animation];
if (BREAK_TABLE_A){
[self.tableView reloadRowsAtIndexPaths:refreshPaths.allObjects withRowAnimation:UITableViewRowAnimationNone];
} else { //BREAK_TABLE_B - I would prefer this solution if possible
for (NSIndexPath *path in refreshPaths){
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:path];
if (cell){
NSUInteger index = [newSection.rows indexOfObjectPassingTest:^BOOL(LQTableViewRow * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [obj.rowIdentifier isEqual:oldSection.rows[path.row].rowIdentifier];
}];
LQTableViewRow *row = newSection.rows[index];
[self configureCell:cell section:newSection row:row];
}
}
}
... update the actual data source ...
[self.tableView endUpdates];
In order to fix this, I've tried wrapping the above code in [CATransaction begin/commit/flush] as well as [UIView begin/commitAnimations] blocks. CATransaction freezes the app with no errors (probably waiting for some other animation) when using table B. UIView causes both views (and all others in the app) to animate incorrectly (cells fade in and animate from bottom to top of their final positions, rather than from the top to the bottom of the tableview).
I've also tried to load the views with dummy data such that they are as similar to each other as possible with the same weird results.
Well, it turns out that the crash when using the CATransaction led me to the answer. I had accidentally been doing [CATransaction begin] twice in one of my cells, instead of committing the transaction. Fixing that fixed the animations.

Expandable list view scrolling in ios

I am using a table view as an expandable list view.I am using section as parent and cells as child.I am getting list view perfectly but the issue is that i want the expanded section view to come into view if the section at end of the screen is clicked. Currently it expands and stay there so one has to scroll it manually.
Thanks.
The easiest way to do that is to call
-[UITableView scrollToRowAtIndexPath:atScrollPosition:animated:]
But be careful. If you call that right after inserting rows for the section, the animation can look pretty bad (cells flying in from weird locations etc.) The easiest solution to that, is to do that after the row insertion animation is completed. Unfortunately, there is no callback for that and the easiest workaround is to use CATransaction callback like so:
// CATransaction is used to be able to have a callback after rows insertion is finished.
// This call opens CATransaction context
[CATransaction begin];
// This call begins tableView updates (not really needed if you only make one insertion call, or one deletion call, but in this example we do both)
[tableView beginUpdates];
// Insert and delete appropriate rows
[tableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationAutomatic];
// completionBlock will be called after rows insertion/deletion animation is done
[CATransaction setCompletionBlock: ^{
// This call will scroll tableView to the top of the 'section' ('section' should have value of the folded/unfolded section's index)
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:NSNotFound inSection:section] // you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}];
// End table view updates
[tableView endUpdates];
// Close CATransaction context
[CATransaction commit];
If you do the folding/unfolding without animation, for example using plain -[UITableView reloadData], you can safely call
-[UITableView scrollToRowAtIndexPath:atScrollPosition:animated:]
directly after -[UITableView reloadData]
like so:
[tableView reloadData];
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:NSNotFound inSection:section] // 'section' is the index of the section you want to be scrolled to the top of the screen
atScrollPosition:UITableViewScrollPositionTop
animated:YES];

Prevent UITableView out of cell bound views from clipping on insertRowsAtIndexPaths:

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];
}

How do you force a specific row to be visible in UITableView when initially loading the table without any animation?

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];

Resources