I'm using insertRowsAtIndexPaths:withRowAnimation, and setting with UITableViewRowAnimationNone. Then i use the the mehod below
Insert UITableView row without ANY animation
But it causes a flash when reload the tableview. How can i fix the flash?
Thank you。
My demo is want to pull to load more local data and show on the tableview,but the last position is previous contentOffset.So i overwrite the UITableView's setContentSize function.
- (void)setContentSize:(CGSize)contentSize {
if (!CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
if (contentSize.height> self.contentSize.height) {
CGPoint offset = self.contentOffset;
offset.y += contentSize.height - self.contentSize.height;
self.contentOffset = offset;
}
}
[super setContentSize:contentSize];
}
When pull to refresh, load the data and refresh tableview:
for (long j = 0; j < result.count; j++) {
NSIndexPath *index = [NSIndexPath indexPathForRow:j inSection:0];
[array addObject:index];
}
[UIView performWithoutAnimation:^{
[_mainTableView beginUpdates];
[_mainTableView insertRowsAtIndexPaths:array withRowAnimation:UITableViewRowAnimationNone];
[_mainTableView endUpdates];
}];
Related
I have a horizontal UICollectionView that implements UICollectionViewLayout and uses targetContentOffsetForProposedContentOffset for a custom fling/paging functionality. Each cell is nearly the width of the screen with the next cell showing slightly to the right. The functionality works great, but I want to be able to delete the cell at index 0 when the current cell is index 1. I'm currently able to calculate and do this just fine, but upon deleting the index 0 cell it slides to the next cell (old index 2, new 1 (after deleting 0)) because of the current content offset. I'm not sure how I can delete index 0 while maintaining the current layout.
Right now in my delegate I'm doing:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float cellWidth = self.collectionView.bounds.size.width - 20;
float currentPage = self.collectionView.contentOffset.x / cellWidth;
if (currentPage == 1) {
[self remove:0];
}
}
-(void)remove:(int)i {
[self.collectionView performBatchUpdates:^{
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished) {
}];
}
So the calculation and deletion works fine, but upon deleting [0], the collection view is scrolled to the next cell..and I'm not sure how to stop it.
I've tried self.collectionview.contentOffset = CGMakePoint(0,0) after the deleting, but the transition is still noticeably buggy. Any ideas on how to approach this problem?
so after a ton of trial and error I was able to resolve the issue. It might be a little hackish but I just wrapped the batch with a uiview animation that has no duration. Basically it overrides the deletion animation
[UIView animateWithDuration:0 animations:^{
[self.collectionView performBatchUpdates:^{
[scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[self.collectionView.collectionViewLayout invalidateLayout];
} completion:nil];
}];
When I attempt to update my table **UITableview** using the code below my cells become smaller. I was wondering if anyone has faced the same problem and would know why this is happening.
NSMutableArray* indexPaths = [[NSMutableArray alloc] init];
for (int i = 20; i < 20 + 20; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimationBottom)];
[self.tableView endUpdates];
....
#CreativityKills.
if your auto layout is on please try following method to add after add rows. It may help you if it was a problem of auto-layout.
NSMutableArray* indexPaths = [[NSMutableArray alloc] init];
for (int i = 20; i < 20 + 20; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimationBottom)];
[self.tableView endUpdates];
[self adjustHeightOfTableview];
- (void)adjustHeightOfTableview
{
CGFloat height = self.tableView.contentSize.height;
CGFloat maxHeight = self.tableView.superview.frame.size.height - self.tableView.frame.origin.y;
// if the height of the content is greater than the maxHeight of
// total space on the screen, limit the height to the size of the
// superview.
if (height > maxHeight)
height = maxHeight;
// now set the height constraint accordingly
[UIView animateWithDuration:0.25 animations:^{
self.tableViewHeightConstraint.constant = height;
[self.view setNeedsUpdateConstraints];
}];
}
Hope this may help you.
May be you have not given implementation for UITableViewDelegate method:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return 44.0;//your default row height
}
I want to add cells at the bottom of a UITableView, and scroll to make it fully visible (just like in Whatsapp when you send or receive a message).
I've tried doing this:
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:[self.tableView numberOfRowsInSection:0]-1 inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
But this makes a graphical glitch and the table flashes and make a bad scrolling instead of a smooth one.
Any help with this?
here what I have done is, inserted a row and then changed the content inset of the tableView
-(IBOutlet)sendButton:(id)sender
{
[array addObject:textView.text];
[self addAnotherRow];
}
- (void)addAnotherRow {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:(array.count - 1) inSection:0];
[self.messagesTableView beginUpdates];
[self.messagesTableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.messagesTableView endUpdates];
[self updateContentInsetForTableView:self.messagesTableView animated:YES];
// TODO: if the scroll offset was at the bottom it can be scrolled down (allow user to scroll up and not override them)
[self.messagesTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
- (void)updateContentInsetForTableView:(UITableView *)tableView animated:(BOOL)animated {
NSUInteger lastRow = [self tableView:tableView numberOfRowsInSection:0];
NSLog(#"last row: %lu", (unsigned long)lastRow);
NSLog(#"items count: %lu", (unsigned long)array.count);
NSUInteger lastIndex = lastRow > 0 ? lastRow - 1 : 0;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:lastIndex inSection:0];
CGRect lastCellFrame = [self.messagesTableView rectForRowAtIndexPath:lastIndexPath];
// top inset = table view height - top position of last cell - last cell height
CGFloat topInset = MAX(CGRectGetHeight(self.messagesTableView.frame) - lastCellFrame.origin.y - CGRectGetHeight(lastCellFrame), 0);
// What about this way? (Did not work when tested)
// CGFloat topInset = MAX(CGRectGetHeight(self.tableView.frame) - self.tableView.contentSize.height, 0);
NSLog(#"top inset: %f", topInset);
UIEdgeInsets contentInset = tableView.contentInset;
contentInset.top = topInset;
NSLog(#"inset: %f, %f : %f, %f", contentInset.top, contentInset.bottom, contentInset.left, contentInset.right);
NSLog(#"table height: %f", CGRectGetHeight(self.messagesTableView.frame));
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:animated ? 0.25 : 0.0 delay:0.0 options:options animations:^{
tableView.contentInset = contentInset;
} completion:^(BOOL finished) {
}];
}
Note: to view existing messages from array, you have to include this
also.
- (void)viewDidLayoutSubviews {
[self updateContentInsetForTableView:self.messagesTableView animated:NO];
}
example function to add an item:
- (IBAction)addItem:(UIBarButtonItem *)sender {
[self.items addObject:[NSString stringWithFormat:#"Item %lu", self.items.count + 1]];
NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForRow:self.items.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPathToInsert] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPathToInsert atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
for me the animation looks perfectly fine this way!
Well, what I did is use [self.tableView scrollToRowAtIndexPath... but calling it from a 0.1s NSTimer.
Make insertions animated and after endUpdates scroll bottom not animating. Worked for me
Swift
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(newIndexPaths, withRowAnimation: UITableViewRowAnimation.Top)
self.tableView.endUpdates()
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.tableDataCount-1, inSection: 0), atScrollPosition: .Bottom, animated: false)
I want to add rows to a tableview with begin/endUpdates to prevent the jump of the tableview when i do reloadData
this is my code
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
[self.messagesTableView beginUpdates];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
tableViewOffset.y += heightForNewRows;
[self.messagesTableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
And sometimes (not everytime) I get this error
invalid number of rows in section 0.
The number of rows contained in an existing section after
the update (2) must be equal to the number of rows contained in that section
before the update (2), plus or minus the number of rows inserted or deleted
from that section (2 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
How do i prevent this error ?
if you dont want animation then use directly
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[self.messagesTableView reloadData];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
If you want to quickly jump to a selected indexPath (with or without animation), you can use this function:
//My function to populate
//tableView is synthesized
-(void)setNewMessage:(NSString*)message{
// ...
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
// if your selectedIndexPath==nil, the table scroll stay in the same position
[self reloadData:selectedIndexPath animated:NO];
}
//My reload data wrapper
-(void)reloadData:(NSIndexPath *)selectedIndexPath animated:(BOOL)animated{
[tableView reloadData];
// atScrollPosition can receive different parameters (eg:UITableViewScrollPositionMiddle)
[tableView scrollToRowAtIndexPath:selectedIndexPath
atScrollPosition:UITableViewScrollPositionTop
animated:animated];
}
Once I needed that same flexibility in a tableView and this function has met my needs. You don`t need the "setAnimationsEnabled", just use:
[tableView scrollToRowAtIndexPath:nil
atScrollPosition:UITableViewScrollPositionNone
animated:NO];
];
I hope it helped.
Try this, I am not sure whether this will work or not.
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.messagesTableView beginUpdates];
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
[self.messagesTableView endUpdates];
});
}
Note: You might require to update rowsInsertIndexPath reference to use inside the block (__block type).
I've got a tableView. When user taps one of records, I check it's property and basing on this I'm filtering results and showing only similar results - so I need to refresh the tableView.
The problem is that user can scroll up/down the tableView. I need to scroll the tableView so the cell is exactly at the same UITableViewScrollPosition as before the refresh.
Obviously, I'm saving last tapped item
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
_lastSelectedItem = [self itemForIndexPath:indexPath];
}
Then after reloading the tableView:
NSIndexPath *indexPath = [self indexPathForItem:_lastSelectedItem];
if (_tableView.numberOfSections > indexPath.section && [_tableView numberOfRowsInSection:indexPath.section] > indexPath.row) {
[_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
_lastSelectedItem = nil;
}
It would be good but... user could not finish at UITableViewScrollPositionMiddle. He could have finish scrolling at UITableViewScrollPositionTop or UITableViewScrollPositionBottom or even somewhere between.
-- edit --
Calculating via offset is also problematic, as the offset is the difference between start of view and top table scroll position.. while I'm not interested in this value :/.
Because my structure was quite complicated I finished to do it this way (eg. expandable sections etc). Please do mind, that I'm saving _lastSelectedItem, which is my object at datasource, as indexPath will change after refresh.
- (void)refresh {
[self saveLastScrollPosition];
[self reloadData]; // inside we reload data + reload tableView
[self scrollToLastScrollPosition];
}
- (NSInteger)heightToMinYOfCellAtIndexPath:(NSIndexPath *)indexPath {
NSInteger sections = indexPath.section;
NSInteger totalRows = 0;
for (NSInteger section = 0; section < indexPath.section; section++) {
totalRows += [self.tableView numberOfRowsInSection:section];
}
totalRows += indexPath.row;
return ((sections + 1) * kHeaderHeight + totalRows * kRowHeight);
}
- (void)saveLastScrollPosition {
if (_lastSelectedItem) { // make sure we have that item selected
NSIndexPath *indexPath = [self itemForItem:_lastSelectedItem];
NSInteger aboveItemHeight = [self heightToMinYOfCellAtIndexPath:indexPath];
CGFloat contentOffsetY = self.tableView.contentOffset.y;
_previousOffset = aboveItemHeight - contentOffsetY;
}
}
- (void)scrollToLastScrollPostion {
if (_lastSelectedItem) { // make sure we have that item selected
NSIndexPath *indexPath = [self itemForItem:_lastSelectedItem];
if (self.tableView.numberOfSections > indexPath.section && [self.tableView numberOfRowsInSection:indexPath.section] > indexPath.row) { // make sure the indexPath still exist after reload
NSInteger aboveItemHeight = [self heightToMinYOfCellAtIndexPath:indexPath]; // height of items above selected index
CGPoint offset = CGPointMake(0.f, aboveItemHeight - _previousOffset);
// in case index should be higher: eg earlier item was at index (4,8) now is at (0,0)
if (offset.y < 0) {
offset.y = 0;
}
[self.tableView setContentOffset:offset animated:NO]; // just jump, user shouldn't see this
_previousOffset = 0; // forget the calculated values
_lastSelectedItem = nil;
}
}
}