我有一个tableView,我在顶部插入行。
虽然我这样做,但我希望当前视图保持完全静止,因此只有在您向上滚动时才显示行。
我已经尝试保存底层UIScrollview的当前位置,并在插入行后重置位置,但这会导致抖动,向上和向下,尽管它最终会回到同一个地方。
有没有很好的方法来实现这个目标?
更新:我使用的是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在没有开始显示的情况下计算出它的新尺寸?
将整个事物包装在beginAnimation commitAnimation中也没什么用。
更新2:这可以清楚地完成 - 当您下载更新时,请参阅官方Twitter应用程序。
答案 0 :(得分:48)
真的没有必要总结所有行高度, 重新加载表后的新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;
...
答案 1 :(得分:24)
-(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;
}
只需将新行的行数传入顶部即可插入。
答案 2 :(得分:21)
@Dian使用图像缓存的方式过于苛刻,我认为这会破坏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
答案 3 :(得分:17)
遇到了同样的问题并找到了解决方案。
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];
...
完美无缺,无故障。
答案 4 :(得分:9)
我使用核心数据样本项目进行了一些测试,并让它静止不动,同时在顶部可见细胞上方添加新细胞。此代码需要调整屏幕上空白空间的表格,但一旦屏幕填满,它就可以正常工作。
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进行测试时,“颤抖”就在那里。似乎单元格插入的动画与动画表格滚动的速度不同。虽然最后的结果可能会以可见单元格的确切位置结束,但整个表格似乎移动了2或3个像素,然后向后移动。
如果动画速度可以相等,它可能会保持不变。
然而,当我按下按钮在上一个动画完成之前添加行时,它会突然停止动画并开始下一个动画,突然改变位置。
答案 5 :(得分:5)
@Dean,
您可以像这样更改代码以防止动画。
[tableView beginUpdates];
[UIView setAnimationsEnabled:NO];
// ...
[tableView endUpdates];
[tableView setContentOffset:newOffset animated:NO];
[UIView setAnimationsEnabled:YES];
答案 6 :(得分:4)
每个人都喜欢复制和粘贴代码示例,所以这里是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
可以替换为调用,以显示指示有新数据的视图。
答案 7 :(得分:3)
我遇到的情况是,由于自定义分组,有很多部分可能在-reloadData调用之间有不同的行数,行高会有所不同。所以这是基于AndreyZ的解决方案。它在-reloadData之前和之后的UIScrollView的contentHeight属性,似乎更普遍。
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];
答案 8 :(得分:2)
我想添加其他条件。 如果您的代码在iOS11或更高版本中,则需要执行以下操作;
在iOS 11中,表格视图默认使用估计的高度。这意味着contentSize最初只是估计值。如果您需要使用contentSize,则需要通过将3个估计高度属性设置为零来禁用估计高度:
tableView.estimatedRowHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0
答案 9 :(得分:1)
你不需要做那么多困难的操作,而且这些操作也不会完美。简单的解决方案是旋转表格视图,然后将单元格旋转到其中。
tableView.transform = CGAffineTransformMakeRotation(M_PI);
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.transform = CGAffineTransformMakeRotation(M_PI);
}
使用[tableView setScrollIndicatorInsets:UIEdgeInsetsMake(0, 0, 0, 310)]
设置滚动指示符的相对位置。在表视图旋转后,它将位于右侧。
答案 10 :(得分:1)
我最终解决了这个问题,将当前的tableview渲染为UIImage,然后在动画时将临时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的增长量,并确保将tableview滚动回到完全相同的位置。
答案 11 :(得分:1)
如果您为tableview返回估计的高度,那么只是抬头似乎不可能这样做。
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath ;
如果你实现这个方法并返回一个粗略的高度,你的tableview将在重新加载时跳转,因为在设置偏移时似乎使用这些高度。
要使其正常工作,请使用上述答案之一(我使用@Mayank Yadav回答),不要实现estimatedHeight方法并缓存单元格高度(记住在插入其他单元格时调整缓存顶部)。
答案 12 :(得分:1)
如何将行添加到表中?
如果您正在更改数据源,然后调用reloadData
,则可能会导致该表再次滚动到顶部。
但是,如果您使用beginUpdates
,insertRowsAtIndexPaths:withRowAnimation:
,endUpdates
方法,则应该能够插入行而无需调用reloadData
,从而将表格保留在其中原来的位置。
在调用endUpdates
之前不要忘记修改数据源,否则最终会出现内部不一致异常。
答案 13 :(得分:1)
禁用动画的简单解决方案
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()
}
答案 14 :(得分:0)
根据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];
答案 15 :(得分:0)
派对晚了,但即使单元格具有动态高度(又名UITableViewAutomaticDimension
),也无需迭代单元格来计算其大小,但只有在tableView的最开头添加项目时才有效并且没有标题,只需要一点点数学,就可以适应每种情况:
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
}
答案 16 :(得分:0)
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
答案 17 :(得分:-1)
如何使用scrollToRowAtIndexPath:atScrollPosition:animated:?您应该只能向数据源添加元素,使用上述方法设置行并重新加载表格...