在顶部插入行时保持uitableview静态

我有一个tableView,我插入行顶部。

虽然我这样做,我希望当前视图保持完全静止,所以行只出现,如果你回滚了。

我已经尝试了保存底层UIScrollview的当前位置,并在插入行之后重置位置,但是这导致了上下抖动,尽pipe它最终返回了同一个位置。

有没有一个很好的方法来实现这一目标?

更新:我正在使用beginUpdate,然后insertRowsAtIndexPath,endUpdates。 没有reloadData调用。

scrollToRowAtIndexPath跳转到当前单元格的顶部(在添加行之前保存)。

我尝试的另一种方法,其结果是完全正确的步伐,但与颤抖是。

save tableView currentOffset. (Underlying scrollView method) Add rows (beginUpdates,insert...,endUpdates) reloadData ( to force a recalulation of the scrollview size ) Recalculate the correct new offset from the bottom of the scrollview setContentOffset (Underlying scrollview method) 

问题是reloadData导致scrollview / tableview开始滚动,然后setContentOffset将它返回到正确的位置。

有没有办法得到一个tableView来解决它的新的大小而不开始显示?

在开始animation提交animation中包装整个事情也无济于事。

更新2:这可以清楚地做到 – 当你拉下更新时,看到一个官方的twitter应用程序。

实际上不需要总结所有的行高度,重新加载表格后新的contentSize已经表示了。 因此,您只需计算contentSize高度的增量并将其添加到当前的偏移量即可。

  ... CGSize beforeContentSize = self.tableView.contentSize; [self.tableView reloadData]; CGSize afterContentSize = self.tableView.contentSize; CGPoint afterContentOffset = self.tableView.contentOffset; CGPoint newContentOffset = CGPointMake(afterContentOffset.x, afterContentOffset.y + afterContentSize.height - beforeContentSize.height); self.tableView.contentOffset = newContentOffset; ... 
 -(void) updateTableWithNewRowCount : (int) rowCount { //Save the tableview content offset CGPoint tableViewOffset = [self.tableView contentOffset]; //Turn of animations for the update block //to get the effect of adding rows on top of TableView [UIView setAnimationsEnabled:NO]; [self.tableView beginUpdates]; NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init]; int heightForNewRows = 0; for (NSInteger i = 0; i < rowCount; i++) { NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:SECTION_TO_INSERT]; [rowsInsertIndexPath addObject:tempIndexPath]; heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath]; } [self.tableView insertRowsAtIndexPaths:rowsInsertIndexPath withRowAnimation:UITableViewRowAnimationNone]; tableViewOffset.y += heightForNewRows; [self.tableView endUpdates]; [UIView setAnimationsEnabled:YES]; [self.tableView setContentOffset:tableViewOffset animated:NO]; } -(int) heightForCellAtIndexPath: (NSIndexPath *) indexPath { UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; int cellHeight = cell.frame.size.height; return cellHeight; } 

只需传入要插入顶部的新行的行数。

@ Dean使用图像caching的方式太过于黑暗,我认为它破坏了UI的响应能力。

一个正确的方法是:使用UITableView子类并覆盖-setContentSize:你可以通过某种方式计算表视图的下推和偏移量,通过设置contentOffset

这是一个最简单的示例代码,用于处理所有插入发生在表视图顶部的最简单情况:

 @implementation MyTableView - (void)setContentSize:(CGSize)contentSize { // I don't want move the table view during its initial loading of content. 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]; } @end 

有同样的问题,并find了解决办法。

  save tableView currentOffset. (Underlying scrollView method) //Add rows (beginUpdates,insert...,endUpdates) // don't do this! reloadData ( to force a recalulation of the scrollview size ) add newly inserted row heights to contentOffset.y here, using tableView:heightForRowAtIndexPath: setContentOffset (Underlying scrollview method) 

喜欢这个:

 - (CGFloat) firstRowHeight { return [self tableView:[self tableView] heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; } ... CGPoint offset = [[self tableView] contentOffset]; [self tableView] reloadData]; offset.y += [self firstRowHeight]; if (offset.y > [[self tableView] contentSize].height) { offset.y = 0; } [[self tableView] setContentOffset:offset]; ... 

完美的作品,没有毛刺。

我做了一个核心数据示例项目的testing,并让它坐在静止,而新的单元格被添加到顶部可见单元格上方。 这个代码需要调整屏幕上空白的表格,但是一旦屏幕被填满,它就可以正常工作。

 static CGPoint delayOffset = {0.0}; - (void)controllerWillChangeContent:(NSFetchedResultsController*)controller { if ( animateChanges ) [self.tableView beginUpdates]; delayOffset = self.tableView.contentOffset; // get the current scroll setting } 

在信元插入点添加了这个。 你可以做细胞删除对手减法。

  case NSFetchedResultsChangeInsert: delayOffset.y += self.tableView.rowHeight; // add for each new row if ( animateChanges ) [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone]; break; 

最后

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if ( animateChanges ) { [self.tableView setContentOffset:delayOffset animated:YES]; [self.tableView endUpdates]; } else { [self.tableView reloadData]; [self.tableView setContentOffset:delayOffset animated:NO]; } } 

使用animateChanges = NO,添加单元格时看不到任何移动。

在animateChanges = YES的testing中,“颤音”在那里。 似乎插入单元格的animation与animation表格滚动的速度并不相同。 虽然最后的结果可能会以可见单元格开始,但整个表格看起来会移动2或3个像素,然后移回。

如果animation速度可以相等,它可能会保持不变。

但是,当我按下button添加行时,在上一个animation完成之前,会突然停止animation并开始下一个animation,导致位置突然变化。

@院长,

你可以像这样改变你的代码,以防止animation。

 [tableView beginUpdates]; [UIView setAnimationsEnabled:NO]; // ... [tableView endUpdates]; [tableView setContentOffset:newOffset animated:NO]; [UIView setAnimationsEnabled:YES]; 

每个人都喜欢复制和粘贴代码的例子,所以这里是Andrey Z.的答案的实现。

这是在我的delegateDidFinishUpdating:(MyDataSourceDelegate*)delegate方法

 if (self.contentOffset.y <= 0) { [self beginUpdates]; [self insertRowsAtIndexPaths:insertedIndexPaths withRowAnimation:insertAnimation]; [self endUpdates]; } else { CGPoint newContentOffset = self.contentOffset; [self reloadData]; for (NSIndexPath *indexPath in insertedIndexPaths) newContentOffset.y += [self.delegate tableView:self heightForRowAtIndexPath:indexPath]; [self setContentOffset:newContentOffset]; NSLog(@"New data at top of table view"); } 

底部的NSLog可以被调用来显示一个视图,显示有新的数据。

我遇到了由于自定义分组而在-reloadData调用之间可能具有不同行数的许多部分的情况,并且行高不同。 所以这里是基于AndreyZ的解决scheme。 它的contentHeight属性UIScrollView之前和之后的重载数据,它似乎更普遍。

 CGFloat contentHeight = self.tableView.contentSize.height; CGPoint offset = self.tableView.contentOffset; [self.tableView reloadData]; offset.y += (self.tableView.contentSize.height - contentHeight); if (offset.y > [self.tableView contentSize].height) offset.y = 0; [self.tableView setContentOffset:offset]; 

你如何将行添加到表中?

如果您要更改数据源,然后调用reloadData ,可能会导致表再次滚动到顶部。

但是,如果使用beginUpdatesinsertRowsAtIndexPaths:withRowAnimation:endUpdates方法,则应该可以插入行而不必调用reloadData从而使表保持原始位置。

在调用endUpdates之前,不要忘记修改你的数据源,否则最终会出现内部不一致的exception。

我最终解决了这个问题,把当前的tableview渲染成一个UIImage,然后在animation的时候把一个临时的UIImageView放在tableview上。

以下代码将生成图像

 // Save the current tableView as an UIImage CSize pageSize = [[self tableView] frame].size; UIGraphicsBeginImageContextWithOptions(pageSize, YES, 0.0); // 0.0 means scale appropriate for device ( retina or no ) CGContextRef resizedContext = UIGraphicsGetCurrentContext(); CGPoint offset = [[self tableView] contentOffset]; CGContextTranslateCTM(resizedContext,-(offset.x),-(offset.y)); [[[self tableView ]layer] renderInContext:resizedContext]; UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 

您需要跟踪在插入行的同时桌面视图的增长量,并确保将tableview滚动回到完全相同的位置。

你不需要做太多困难的操作,而且这些操作也不能很好地工作。 简单的解决scheme是旋转表视图,然后旋转单元格。

 tableView.transform = CGAffineTransformMakeRotation(M_PI); -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { cell.transform = CGAffineTransformMakeRotation(M_PI); } 

使用[tableView setScrollIndicatorInsets:UIEdgeInsetsMake(0, 0, 0, 310)]来设置滚动指标的相对位置。 在表视图旋转后,它将在右侧。

如果你返回tableview的估计高度的话,看起来似乎不可能做到这一点。

  - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath ; 

如果你实现这个方法并且返回一个粗略的高度,你的tableview会在重新加载的时候跳转,因为在设置偏移的时候它似乎会使用这些高度。

为了得到它的工作使用上面的答案之一(我去了@Mayank亚达夫答案),不要实现estimatedHeight方法和caching单元格高度(记住调整caching,当你插入额外的单元格在顶部)。

简单的解决scheme来禁用animation

 func addNewRows(indexPaths: [NSIndexPath]) { let addBlock = { () -> Void in self.tableView.beginUpdates() self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .None) self.tableView.endUpdates() } tableView.contentOffset.y >= tableView.height() ? UIView.performWithoutAnimation(addBlock) : addBlock() } 

基于Andrey Z的回答,这里有一个适合我的实例。

 int numberOfRowsBeforeUpdate = [controller.tableView numberOfRowsInSection:0]; CGPoint currentOffset = controller.tableView.contentOffset; if(numberOfRowsBeforeUpdate>0) { [controller.tableView reloadData]; int numberOfRowsAfterUpdate = [controller.tableView numberOfRowsInSection:0]; float rowHeight = [controller getTableViewCellHeight]; //custom method in my controller float offset = (numberOfRowsAfterUpdate-numberOfRowsBeforeUpdate)*rowHeight; if(offset>0) { currentOffset.y = currentOffset.y+offset; [controller.tableView setContentOffset:currentOffset]; } } else [controller.tableView reloadData]; 

晚了,但这个工程,即使单元格有dynamic高度(又名UITableViewAutomaticDimension ),不需要迭代单元格来计算它们的大小,但只有当项目被添加在tableView的最开始,没有头,与有一点math可能可以适应这种情况:

 func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { if indexPath.row == 0 { self.getMoreMessages() } } private func getMoreMessages(){ var initialOffset = self.tableView.contentOffset.y self.tableView.reloadData() //@numberOfCellsAdded: number of items added at top of the table self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: numberOfCellsAdded, inSection: 0), atScrollPosition: .Top, animated: false) self.tableView.contentOffset.y += initialOffset } 

AmitP回答,Swift 3版本

 let beforeContentSize = self.tableView.contentSize self.tableView.reloadData() let afterContentSize = self.tableView.contentSize let afterContentOffset = self.tableView.contentOffset let newContentOffset = CGPoint(x: afterContentOffset.x, y: afterContentOffset.y + afterContentSize.height - beforeContentSize.height) self.tableView.contentOffset = newContentOffset 

如何使用scrollToRowAtIndexPath:atScrollPosition:animated:? 你应该能够添加一个元素到你的数据源中,用上面提到的方法设置这个行并重新加载表格…