在顶部插入行时保持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
,可能会导致表再次滚动到顶部。
但是,如果使用beginUpdates
, insertRowsAtIndexPaths: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:? 你应该能够添加一个元素到你的数据源中,用上面提到的方法设置这个行并重新加载表格…