UICollectionView 在保持位置上方插入单元格(如 Messages.app)
Posted
技术标签:
【中文标题】UICollectionView 在保持位置上方插入单元格(如 Messages.app)【英文标题】:UICollectionView insert cells above maintaining position (like Messages.app) 【发布时间】:2014-08-28 11:58:35 【问题描述】:默认情况下,集合视图在插入单元格时保持内容偏移。另一方面,我想在当前显示的单元格上方插入单元格,以便它们出现在屏幕顶部边缘上方,就像 Messages.app 在您加载早期消息时所做的那样。有谁知道实现它的方法吗?
【问题讨论】:
【参考方案1】:这是我使用的技术。我发现其他人会导致奇怪的副作用,例如屏幕闪烁:
CGFloat bottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y;
[CATransaction begin];
[CATransaction setDisableActions:YES];
[self.collectionView performBatchUpdates:^
[self.collectionView insertItemsAtIndexPaths:indexPaths];
completion:^(BOOL finished)
self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - bottomOffset);
];
[CATransaction commit];
【讨论】:
效果很好,谢谢!请参阅下面的 Swift 2 版本。 如果我对布局进行更改[self.tableView reloadItemsAtIndexPaths:indexPathsToUpdate];
,您的解决方案将不起作用(不幸的是)。
效果很好,只是更新时有轻微的闪烁,但还不错。除了把整个事情颠倒过来之外,我发现的最好的。当我进行翻转时,它使我的收藏视图非常滞后,因为它里面有很多东西。所以对我来说这是最好的。
什么是索引路径?
去除闪烁可以试试@Peter Stajger的方法【参考方案2】:
James Martin 的精彩版本转换为 Swift 2:
let amount = 5 // change this to the amount of items to add
let section = 0 // change this to your needs, too
let contentHeight = self.collectionView!.contentSize.height
let offsetY = self.collectionView!.contentOffset.y
let bottomOffset = contentHeight - offsetY
CATransaction.begin()
CATransaction.setDisableActions(true)
self.collectionView!.performBatchUpdates(
var indexPaths = [NSIndexPath]()
for i in 0..<amount
let index = 0 + i
indexPaths.append(NSIndexPath(forItem: index, inSection: section))
if indexPaths.count > 0
self.collectionView!.insertItemsAtIndexPaths(indexPaths)
, completion:
finished in
print("completed loading of new stuff, animating")
self.collectionView!.contentOffset = CGPointMake(0, self.collectionView!.contentSize.height - bottomOffset)
CATransaction.commit()
)
【讨论】:
你可以这样做:让 indexPaths = (0...amount).mapNSIndexPath(forItem: $0, inSection: section) 如何隐藏collectionview的滚动??【参考方案3】:我的方法利用子类流布局。这意味着您不必破解视图控制器中的滚动/布局代码。想法是,只要您知道要在顶部插入单元格,就可以设置自定义属性,然后标记下一次布局更新将在顶部插入单元格,并且您会记住更新前的内容大小。然后覆盖 prepareLayout() 并在那里设置所需的内容偏移量。它看起来像这样:
定义变量
private var isInsertingCellsToTop: Bool = false
private var contentSizeWhenInsertingToTop: CGSize?
覆盖 prepareLayout()
并在调用super之后
if isInsertingCellsToTop == true
if let collectionView = collectionView, oldContentSize = contentSizeWhenInsertingToTop
let newContentSize = collectionViewContentSize()
let contentOffsetY = collectionView.contentOffset.y + (newContentSize.height - oldContentSize.height)
let newOffset = CGPointMake(collectionView.contentOffset.x, contentOffsetY)
collectionView.setContentOffset(newOffset, animated: false)
contentSizeWhenInsertingToTop = nil
isInsertingMessagesToTop = false
【讨论】:
这可能是迄今为止最好的选择。看起来很有希望 仅在使用 UIView.performWithoutAnimation(self.collectionView?.insertSections(NSIndexSet(index: 0))) 插入新部分时对我有用,但滚动突然停止。有没有办法实现连续滚动,即在 CollectionView 边界上方“静默”插入部分? @hacker2007 尝试使用 collectionView.contentOffset = newOffset 而不是 setContentOffset 这对我有用。 很棒的方法。截至 2017 年 5 月 30 日,您的代码存在一些小问题 - 我在下面发布了您的答案的 Swift 3 版本。谢谢彼得! 这是迄今为止我找到的更简洁简洁的解决方案。感谢分享。【参考方案4】:我在两行代码中做到了这一点(虽然它是在 UITableView 上),但我认为你可以用同样的方式做到这一点。
我将 tableview 旋转了 180 度。
然后我将每个 tableview 单元格也旋转了 180 度。
这意味着我可以将其视为标准的从上到下的表格,但底部被视为顶部。
【讨论】:
通过 (1,-1) 更好地缩放它 @k06a 想解释一下为什么?您仍然必须将单元格也按 (1, -1) 缩放。不一样吗? @Fogmeister 更好地缩放表格 (1,-1) 和单元格 (1,-1)。如果您旋转,您也会将滚动指示器翻转到屏幕的另一侧。反映在 x 轴上会保持滚动指示器。 @AndyPoes 啊很好。在我的应用程序中,我不需要滚动指示器,但是是的。这绝对是有道理的:-) @k06a 太棒了。直到我读到你的评论才注意到这一点。竖起大拇指【参考方案5】:这是 Peter 解决方案的略微调整版本(子类化流布局,不颠倒,轻量级方法)。它是 Swift 3。注意UIView.animate
的持续时间为零 - 这是为了允许单元格的偶数/奇数动画(一行上的内容)动画,但停止视口偏移变化的动画(这看起来很糟糕)
用法:
let layout = self.collectionview.collectionViewLayout as! ContentSizePreservingFlowLayout
layout.isInsertingCellsToTop = true
self.collectionview.performBatchUpdates(
if let deletionIndexPaths = deletionIndexPaths, deletionIndexPaths.count > 0
self.collectionview.deleteItems(at: deletionIndexPaths.map return IndexPath.init(item: $0.item+twitterItems, section: 0) )
if let insertionIndexPaths = insertionIndexPaths, insertionIndexPaths.count > 0
self.collectionview.insertItems(at: insertionIndexPaths.map return IndexPath.init(item: $0.item+twitterItems, section: 0) )
) (finished) in
completionBlock?()
这里是ContentSizePreservingFlowLayout
的全部内容:
class ContentSizePreservingFlowLayout: UICollectionViewFlowLayout
var isInsertingCellsToTop: Bool = false
didSet
if isInsertingCellsToTop
contentSizeBeforeInsertingToTop = collectionViewContentSize
private var contentSizeBeforeInsertingToTop: CGSize?
override func prepare()
super.prepare()
if isInsertingCellsToTop == true
if let collectionView = collectionView, let oldContentSize = contentSizeBeforeInsertingToTop
UIView.animate(withDuration: 0, animations:
let newContentSize = self.collectionViewContentSize
let contentOffsetY = collectionView.contentOffset.y + (newContentSize.height - oldContentSize.height)
let newOffset = CGPoint(x: collectionView.contentOffset.x, y: contentOffsetY)
collectionView.contentOffset = newOffset
)
contentSizeBeforeInsertingToTop = nil
isInsertingCellsToTop = false
【讨论】:
这对我有用:) 只需要提几件事:“如果 isInsertingCellsToTop == true”可以简单地阅读“如果 isInsertingCellsToTop”,因为它是一个布尔值,我替换了“UIView.animate(withDuration : 0, 动画:" 带有 UIView.performWithoutAnimation。【参考方案6】:Swift 3 版本代码: 基于 James Martin 的回答
let amount = 1 // change this to the amount of items to add
let section = 0 // change this to your needs, too
let contentHeight = self.collectionView.contentSize.height
let offsetY = self.collectionView.contentOffset.y
let bottomOffset = contentHeight - offsetY
CATransaction.begin()
CATransaction.setDisableActions(true)
self.collectionView.performBatchUpdates(
var indexPaths = [NSIndexPath]()
for index in 0..<amount
indexPaths.append(NSIndexPath(item: index, section: section))
if indexPaths.count > 0
self.collectionView.insertItems(at: indexPaths as [IndexPath])
, completion:
finished in
print("completed loading of new stuff, animating")
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
CATransaction.commit()
)
【讨论】:
CATransaction
object 拯救我的一天。很好的解决方案。【参考方案7】:
添加到 Fogmeister 的答案(使用代码),最干净的方法是反转(倒置)UICollectionView
,这样您的滚动视图就会粘在底部而不是顶部。正如 Fogmeister 所指出的,这也适用于 UITableView
。
- (void)viewDidLoad
[super viewDidLoad];
self.collectionView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0);
在 Swift 中:
override func viewDidLoad()
super.viewDidLoad()
collectionView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0)
这有一个副作用是你的单元格也会倒置显示,所以你也必须翻转它们。所以我们像这样传输 trasform (cell.transform = collectionView.transform
):
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
cell.transform = collectionView.transform;
return cell;
在 Swift 中:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! UICollectionViewCell
cell.transform = collectionView.transform
return cell
最后,在这种设计下开发时要记住的主要事情是委托中的NSIndexPath
参数是相反的。所以indexPath.row == 0
是collectionView
底部的行,它通常位于顶部。
许多开源项目都使用这种技术来产生所描述的行为,包括由Slack 维护的流行的 SlackTextViewController (https://github.com/slackhq/SlackTextViewController)
我想我会在 Fogmeister 的精彩回答中添加一些代码上下文!
【讨论】:
这种方法非常适用于静态内容,但是当调用 self.collectionView.insertItemsAtIndexPaths(indexPaths) 时,collectionView 会执行一些动画,导致单元格被翻转到它们的原始方向。你有没有遇到过这个并设法解决它? @JonAndersen 我没有经历过这个。您可以控制这些动画:(objc.io/issues/12-animations/collectionview-animations)。你用的是定制的吗?如果不需要,你需要动画吗?您可以尝试将动画移除或更改为不影响变换的动画(即淡入)。 我尝试使用没有任何动画的自定义布局,但它仍然有一些我无法找到和停止的动画。谢谢 @JonAndersen 你有想过这个吗?我在使用 insertItemsAtIndexPaths 时遇到了与您描述的相同的问题,但无法修复。尝试创建 UICollectionViewFlowLayout 的子类并使用与集合视图相同的转换覆盖 initialLayoutAttributesForAppearing .. 没有做任何事情。谢谢 设置UICollectionViewCell.transform
对我不起作用,因为动画 (animateWithDuration
/performBatchUpdates
) 践踏了转换。相反,设置UICollectionViewCell.contentView.transform
效果更好。【参考方案8】:
这是我从JSQMessagesViewController: How maintain scroll position? 学到的。非常简单,好用,无闪烁!
// Update collectionView dataSource
data.insert(contentsOf: array, at: startRow)
// Reserve old Offset
let oldOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y
// Update collectionView
collectionView.reloadData()
collectionView.layoutIfNeeded()
// Restore old Offset
collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - oldOffset)
【讨论】:
辉煌——超干净的结果 在用户不滚动但滚动时会出现问题【参考方案9】:喜欢 James Martin 的解决方案。但对我来说,在特定内容窗口的上方/下方插入/删除时,它开始崩溃。我尝试子类化 UICollectionViewFlowLayout 以获得我想要的行为。希望这可以帮助某人。任何反馈表示赞赏:)
@interface FixedScrollCollectionViewFlowLayout ()
__block float bottomMostVisibleCell;
__block float topMostVisibleCell;
@property (nonatomic, assign) BOOL isInsertingCellsToTop;
@property (nonatomic, strong) NSArray *visableAttributes;
@property (nonatomic, assign) float offset;;
@end
@implementation FixedScrollCollectionViewFlowLayout
- (id)initWithCoder:(NSCoder *)aDecoder
self = [super initWithCoder:aDecoder];
if (self)
_isInsertingCellsToTop = NO;
return self;
- (id)init
self = [super init];
if (self)
_isInsertingCellsToTop = NO;
return self;
- (void)prepareLayout
NSLog(@"prepareLayout");
[super prepareLayout];
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
NSLog(@"layoutAttributesForElementsInRect");
self.visableAttributes = [super layoutAttributesForElementsInRect:rect];
self.offset = 0;
self.isInsertingCellsToTop = NO;
return self.visableAttributes;
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
bottomMostVisibleCell = -MAXFLOAT;
topMostVisibleCell = MAXFLOAT;
CGRect container = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, self.collectionView.frame.size.width, self.collectionView.frame.size.height);
[self.visableAttributes enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *attributes, NSUInteger idx, BOOL *stop)
CGRect currentCellFrame = attributes.frame;
CGRect containerFrame = container;
if(CGRectIntersectsRect(containerFrame, currentCellFrame))
float x = attributes.indexPath.row;
if (x < topMostVisibleCell) topMostVisibleCell = x;
if (x > bottomMostVisibleCell) bottomMostVisibleCell = x;
];
NSLog(@"prepareForCollectionViewUpdates");
[super prepareForCollectionViewUpdates:updateItems];
for (UICollectionViewUpdateItem *updateItem in updateItems)
switch (updateItem.updateAction)
case UICollectionUpdateActionInsert:
NSLog(@"UICollectionUpdateActionInsert %ld",updateItem.indexPathAfterUpdate.row);
if (topMostVisibleCell>updateItem.indexPathAfterUpdate.row)
UICollectionViewLayoutAttributes * newAttributes = [self layoutAttributesForItemAtIndexPath:updateItem.indexPathAfterUpdate];
self.offset += (newAttributes.size.height + self.minimumLineSpacing);
self.isInsertingCellsToTop = YES;
break;
case UICollectionUpdateActionDelete:
NSLog(@"UICollectionUpdateActionDelete %ld",updateItem.indexPathBeforeUpdate.row);
if (topMostVisibleCell>updateItem.indexPathBeforeUpdate.row)
UICollectionViewLayoutAttributes * newAttributes = [self layoutAttributesForItemAtIndexPath:updateItem.indexPathBeforeUpdate];
self.offset -= (newAttributes.size.height + self.minimumLineSpacing);
self.isInsertingCellsToTop = YES;
break;
case UICollectionUpdateActionMove:
NSLog(@"UICollectionUpdateActionMoveB %ld", updateItem.indexPathBeforeUpdate.row);
break;
default:
NSLog(@"unhandled case: %ld", updateItem.indexPathBeforeUpdate.row);
break;
if (self.isInsertingCellsToTop)
if (self.collectionView)
[CATransaction begin];
[CATransaction setDisableActions:YES];
- (void)finalizeCollectionViewUpdates
CGPoint newOffset = CGPointMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y + self.offset);
if (self.isInsertingCellsToTop)
if (self.collectionView)
self.collectionView.contentOffset = newOffset;
[CATransaction commit];
【讨论】:
【参考方案10】:受 Bryan Pratte 的解决方案的启发,我开发了 UICollectionViewFlowLayout 的子类来获得聊天行为而不会将集合视图倒置。此布局是用 Swift 3 编写的,并且绝对可以与 RxSwift 和 RxDataSources 一起使用,因为 UI 与任何逻辑或绑定完全分离。
三件事对我来说很重要:
-
如果有新消息,请向下滚动到它。此时此刻,您在列表中的哪个位置并不重要。滚动是用
setContentOffset
而不是scrollToItemAtIndexPath
实现的。
如果您对较旧的消息执行“延迟加载”,则滚动视图不应更改并保持在原来的位置。
在开头添加例外。集合视图应该表现“正常”,直到屏幕上的消息多于空间。
我的解决方案: https://gist.github.com/jochenschoellig/04ffb26d38ae305fa81aeb711d043068
【讨论】:
效果很好(即使存在 refreshControl)。非常感谢 ! +1 看起来不错!我正在使用以下代码在我们的聊天应用程序中进行更改:github.com/RocketChat/Rocket.Chat.ios/pull/577。谢谢!【参考方案11】:虽然上述所有解决方案都对我有用,但失败的主要原因是当用户在添加这些项目时滚动时,滚动要么停止,要么会有明显的延迟 这是一个在将项目添加到顶部时有助于保持(视觉)滚动位置的解决方案。
class Layout: UICollectionViewFlowLayout
var heightOfInsertedItems: CGFloat = 0.0
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint
var offset = proposedContentOffset
offset.y += heightOfInsertedItems
heightOfInsertedItems = 0.0
return offset
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
var offset = proposedContentOffset
offset.y += heightOfInsertedItems
heightOfInsertedItems = 0.0
return offset
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem])
super.prepare(forCollectionViewUpdates: updateItems)
var totalHeight: CGFloat = 0.0
updateItems.forEach item in
if item.updateAction == .insert
if let index = item.indexPathAfterUpdate
if let attrs = layoutAttributesForItem(at: index)
totalHeight += attrs.frame.height
self.heightOfInsertedItems = totalHeight
这个布局会记住即将插入的item的高度,然后下次layout需要offset的时候,会用添加的item的高度来补偿offset。
【讨论】:
你能提供一个如何使用这个的例子【参考方案12】:不是我现在坚持的最优雅但非常简单且有效的解决方案。仅适用于线性布局(不是网格),但对我来说没问题。
// retrieve data to be inserted
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
NSMutableArray *objects = [fetchedObjects mutableCopy];
[objects addObjectsFromArray:self.messages];
// self.messages is a DataSource array
self.messages = objects;
// calculate index paths to be updated (we are inserting
// fetchedObjects.count of objects at the top of collection view)
NSMutableArray *indexPaths = [NSMutableArray new];
for (int i = 0; i < fetchedObjects.count; i ++)
[indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:0]];
// calculate offset of the top of the displayed content from the bottom of contentSize
CGFloat bottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y;
// performWithoutAnimation: cancels default collection view insertion animation
[UIView performWithoutAnimation:^
// capture collection view image representation into UIImage
UIGraphicsBeginImageContextWithOptions(self.collectionView.bounds.size, NO, 0);
[self.collectionView drawViewHierarchyInRect:self.collectionView.bounds afterScreenUpdates:YES];
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// place the captured image into image view laying atop of collection view
self.snapshot.image = snapshotImage;
self.snapshot.hidden = NO;
[self.collectionView performBatchUpdates:^
// perform the actual insertion of new cells
[self.collectionView insertItemsAtIndexPaths:indexPaths];
completion:^(BOOL finished)
// after insertion finishes, scroll the collection so that content position is not
// changed compared to such prior to the update
self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - bottomOffset);
[self.collectionView.collectionViewLayout invalidateLayout];
// and hide the snapshot view
self.snapshot.hidden = YES;
];
];
【讨论】:
在滚动加载更多数据后,我尝试使用这种方式滚动到下一项。它不起作用,它将我的 UICollectionview 滚动到顶部 @mrvn 约束违反在这方面有什么作用吗? @mrvn【参考方案13】:if ([newMessages count] > 0)
[self.collectionView reloadData];
if (hadMessages)
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:[newMessages count] inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
到目前为止,这似乎有效。重新加载集合,在没有动画的情况下将之前的第一条消息滚动到顶部。
【讨论】:
【参考方案14】:我设法编写了一个解决方案,适用于同时在顶部和底部插入单元格的情况。
-
保存顶部可见单元格的位置。计算导航栏下方单元格的高度(顶视图。在我的例子中是 self.participantsView)
// get the top cell and save frame
NSMutableArray<NSIndexPath*> *visibleCells = [self.collectionView indexPathsForVisibleItems].mutableCopy;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"item" ascending:YES];
[visibleCells sortUsingDescriptors:@[sortDescriptor]];
ChatMessage *m = self.chatMessages[visibleCells.firstObject.item];
UICollectionViewCell *topCell = [self.collectionView cellForItemAtIndexPath:visibleCells.firstObject];
CGRect topCellFrame = topCell.frame;
CGRect navBarFrame = [self.view convertRect:self.participantsView.frame toView:self.collectionView];
CGFloat offset = CGRectGetMaxY(navBarFrame) - topCellFrame.origin.y;
-
重新加载您的数据。
[self.collectionView reloadData];
-
获取项目的新位置。获取该索引的属性。提取偏移量并更改collectionView的contentOffset。
// scroll to the old cell position
NSUInteger messageIndex = [self.chatMessages indexOfObject:m];
UICollectionViewLayoutAttributes *attr = [self.collectionView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:messageIndex inSection:0]];
self.collectionView.contentOffset = CGPointMake(0, attr.frame.origin.y + offset);
【讨论】:
【参考方案15】:// stop scrolling
setContentOffset(contentOffset, animated: false)
// calculate the offset and reloadData
let beforeContentSize = contentSize
reloadData()
layoutIfNeeded()
let afterContentSize = contentSize
// reset the contentOffset after data is updated
let newOffset = CGPoint(
x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),
y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))
setContentOffset(newOffset, animated: false)
【讨论】:
【参考方案16】:我发现这五个步骤可以无缝地工作:
为新单元格准备数据,并根据需要插入数据
告诉UIView
停止动画
UIView.setAnimationsEnabled(false)
实际插入那些单元格
collectionView?.insertItems(at: indexPaths)
滚动集合视图(UIScrollView
的子类)
scrollView.contentOffset.y += CELL_HEIGHT * CGFloat(ITEM_COUNT)
注意将 CELL_HEIGHT 替换为单元格的高度(这仅在单元格大小固定时才容易实现)。添加任何单元格到单元格的边距/插图很重要。
记得告诉UIView
重新开始动画:
UIView.setAnimationsEnabled(true)
【讨论】:
【参考方案17】:一些建议的方法对我来说取得了不同程度的成功。我最终使用了子类化的变体和prepareLayout
选项 Peter Stajger 将我的偏移校正放在finalizeCollectionViewUpdates
中。然而今天,当我查看一些额外的文档时,我发现了targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint)
,我认为这更像是此类更正的预期位置。所以这是我使用它的实现。请注意,我的实现是针对水平集合,但 cellsInsertingToTheLeft
可以轻松更新为 cellsInsertingAbove
并相应地纠正偏移量。
class GCCFlowLayout: UICollectionViewFlowLayout
var cellsInsertingToTheLeft: Int?
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint
guard let cells = cellsInsertingToTheLeft else return proposedContentOffset
guard let collectionView = collectionView else return proposedContentOffset
let contentOffsetX = collectionView.contentOffset.x + CGFloat(cells) * (collectionView.bounds.width - 45 + 8)
let newOffset = CGPoint(x: contentOffsetX, y: collectionView.contentOffset.y)
cellsInsertingToTheLeft = nil
return newOffset
【讨论】:
【参考方案18】:根据@Steven 的回答,我设法使插入单元格滚动到底部,没有任何闪烁(并使用自动单元格),在 iOS 12 上进行了测试
let oldOffset = self.collectionView!.contentOffset
let oldOffsetDelta = self.collectionView!.contentSize.height - self.collectionView!.contentOffset.y
CATransaction.begin()
CATransaction.setCompletionBlock
self.collectionView!.setContentOffset(CGPoint(x: 0, y: self.collectionView!.contentSize.height - oldOffsetDelta), animated: true)
collectionView!.reloadData()
collectionView!.layoutIfNeeded()
self.collectionView?.setContentOffset(oldOffset, animated: false)
CATransaction.commit()
【讨论】:
【参考方案19】:我使用了@James Martin 方法,但如果您使用coredata
和NSFetchedResultsController
,正确的方法是将之前加载的消息的数量存储在_earlierMessagesLoaded
中并检查值controllerDidChangeContent:
#pragma mark - NSFetchedResultsController
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
if(_earlierMessagesLoaded)
__block NSMutableArray * indexPaths = [NSMutableArray new];
for (int i =0; i<[_earlierMessagesLoaded intValue]; i++)
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
CGFloat bottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y;
[CATransaction begin];
[CATransaction setDisableActions:YES];
[self.collectionView performBatchUpdates:^
[self.collectionView insertItemsAtIndexPaths:indexPaths];
completion:^(BOOL finished)
self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - bottomOffset);
[CATransaction commit];
_earlierMessagesLoaded = nil;
];
else
[self finishReceivingMessageAnimated:NO];
【讨论】:
【参考方案20】:CGPoint currentOffset = _collectionView.contentOffset;
CGSize contentSizeBeforeInsert = [_collectionView.collectionViewLayout collectionViewContentSize];
[_collectionView reloadData];
CGSize contentSizeAfterInsert = [_collectionView.collectionViewLayout collectionViewContentSize];
CGFloat deltaHeight = contentSizeAfterInsert.height - contentSizeBeforeInsert.height;
currentOffset.y += MAX(deltaHeight, 0);
_collectionView.contentOffset = currentOffset;
【讨论】:
以上是关于UICollectionView 在保持位置上方插入单元格(如 Messages.app)的主要内容,如果未能解决你的问题,请参考以下文章
在 UICollectionView 中重新加载数据后保持 collectionview 速度
将 UIRefreshControl 置于滚动 UICollectionView 上方的正确方法是啥?
UICollectionView-如何删除第一个单元格上方的间距?