删除 UICollectionView 边缘的单元格 - 滚动后单元格不会立即出现 [英] Deleting cell at edge of UICollectionView - cells not appearing immediately after scroll

查看:19
本文介绍了删除 UICollectionView 边缘的单元格 - 滚动后单元格不会立即出现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑一个标准的垂直滚动流布局,其中填充了足够的单元格以引起滚动.当滚动到底部时,如果您删除一个项目,使得集合视图的内容大小必须缩小以容纳新的项目数(即删除最底行的最后一个项目),从滚动的单元格行顶部被隐藏.在删除动画结束时,顶行出现没有动画——这是一个非常不愉快的效果.

Consider an standard, vertically scrolling flow layout populated with enough cells to cause scrolling. When scrolled to the bottom, if you delete an item such that the content size of the collection view must shrink to accommodate the new number of items (i.e. delete the last item on the bottom row), the row of cells that scroll in from the top are hidden. At the end of the deletion animation, the top row appears without animation - it's a very unpleasant effect.

慢动作:

复制真的很简单:

  1. 新建一个单视图项目,将默认的ViewController改为UICollectionViewController

UICollectionViewController 添加到使用标准流布局的情节提要中,并将其类更改为 ViewController.为单元原型指定标识符Cell",大小为 200x200.

Add a UICollectionViewController to the storyboard that uses a standard flow layout, and change its class to ViewController. Give the cell prototype the identifier "Cell" and a size of 200x200.

将以下代码添加到ViewController.m:


@interface ViewController ()
@property(nonatomic, assign) NSInteger numberOfItems;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.numberOfItems = 19;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return self.numberOfItems;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    self.numberOfItems--;
    [collectionView deleteItemsAtIndexPaths:@[indexPath]];
}

@end


我在处理集合视图时看到了这个问题的其他表现,只是上面的例子似乎最简单地说明了这个问题.UICollectionView 在默认动画期间似乎进入了某种恐慌的瘫痪状态,并且在动画完成之前拒绝取消隐藏某些单元格.它甚至可以防止手动调用隐藏单元格上的 cell.hidden = NO 产生影响(之后 hidden 仍然是 YES).下拉到底层并在那里设置 hidden 是可行的,前提是您可以获得对要取消隐藏的单元格的引用,这在处理尚未显示的单元格时非常重要.

I've seen other manifestations of this problem when dealing with collection views, it's just that the above example seems the simplest to demonstrate the issue. UICollectionView seems to go into some kind of paralysed state of panic during the default animations, and refuses to unhide certain cells until after the animation completes. It even prevents manual calls to cell.hidden = NO on hidden cells from having an effect (hidden is still YES afterwards). Dropping down to the underlying layer and setting hidden there works, provided you can get a reference to the cell you want to unhide, which is non-trivial when dealing with cells that haven't been displayed yet.

-initialLayoutAttributesForAppearingItemAtIndexPath 正在为调用 deleteItemsAtIndexPaths: 时可见的每个项目调用,但不会为滚动到视图中的项目调用.可以通过在之后立即在批处理更新块中调用 reloadData 来解决此问题,这似乎使集合视图意识到顶行即将出现:

-initialLayoutAttributesForAppearingItemAtIndexPath is being called for every item visible at the time of the call to deleteItemsAtIndexPaths:, but not for the ones that are scrolled into view. It is possible work around the issue by calling reloadData inside a batch update block immediately afterwards, which appears to make the collection view realise that the top row is about to appear:

[collectionView deleteItemsAtIndexPaths:@[indexPath]];
[collectionView performBatchUpdates:^{
    [collectionView reloadData];
} completion:nil];

但不幸的是,这不是我的选择.我正在尝试通过操作单元格层来实现一些自定义动画时间 &动画,并且调用 reloadData 确实会导致不必要的布局回调,从而使事情变得混乱.

But unfortunately this is not an option for me. I am trying to implement some custom animation timing by manipulating the cell layers & animations, and calling reloadData really throws things out of whack by causing unnecessary layout callbacks.


我在许多布局方法中添加了日志语句,并查看了一些堆栈帧以尝试找出问题所在.至关重要的是,我正在检查何时调用 layoutSubviews、何时集合视图要求来自布局对象 (layoutAttributesForElementsInRect:) 的布局属性以及何时 applyLayoutAttributes:.

I added log statements to a lot of layout methods and looked through some stack frames to try and find out what's going wrong. Crucially, I'm checking when layoutSubviews is called, when the collection view asks for layout attributes from the layout object (layoutAttributesForElementsInRect:) and when applyLayoutAttributes: is called on the cells.

我希望看到这样的一系列方法:

I would expect to see a sequence of methods like this:

// user taps cell (to delete it)
-deleteItemsAtIndexPaths:
-layoutAttributesForElementsInRect:
-finalLayoutAttributes...:                // Called for the item being deleted
-finalLayoutAttributes...:                // \__ Called for each index path visible
-initialLayoutAttributes...:              // /   when deletion started
-applyLayoutAttributes:                   // Called for the item being deleted, to apply final layout attributes
// collection view begins scrolling up
-layoutSubviews:                          // Called multiple times as the 
-layoutAttributesForElementsInRect:       // collection view scrolls
// ... for any new set of
// ... attributes returned:
-collectionView:cellForItemAtIndexPath:
-applyLayoutAttributes:                   // Sets the standard attributes for the new cell
// collection view finishes scrolling

大部分情况正在发生;视图滚动时正确触发布局,并且集合视图正确查询布局以获取要显示的单元格的属性.但是,collectionView:cellForItemAtIndexPath: 和相应的 applyLayoutAttributes: 方法直到删除后才被调用,当最后一次调用布局时,会导致隐藏单元格被分配它们的布局属性(设置 hidden = NO).

Most of this is happening; layout is correctly triggered as the view scrolls, and the collection view properly queries the layout for the attributes of cells to be displayed. However, collectionView:cellForItemAtIndexPath: and the corresponding applyLayoutAttributes: methods are not being called until after the deletion, when layout is invoked one last time causing the hidden cells to be assigned their layout attributes (sets hidden = NO).

因此,尽管从布局对象接收到所有正确响应,但集合视图似乎设置了某种标志以在更新期间不更新单元格.UICollectionView 上有一个从 layoutSubviews 中调用的私有方法,该方法似乎负责刷新单元格的外观:_updateVisibleCellsNow:.这是在应用单元格起始属性之前最终要求数据源提供新单元格的地方,这似乎是失败点,因为它没有在应该调用的时候被调用.

So it seems that despite receiving all the correct responses from the layout object, the collection view has some kind of flag set to not update the cells during the update. There is a private method on UICollectionView called from within layoutSubviews that seems responsible for refreshing the cells' appearance: _updateVisibleCellsNow:. This is from where the data source eventually gets asked for a new cell before applying the cells starting attributes, and it seems this is the point of failure, as it is not being called when it should be.


此外,这似乎与更新动画有关,或者至少在插入/删除的持续时间内没有更新单元格.例如以下没有故障的工作:

Additionally, this does seem to be related to the update animation, or at least cells are not updated for the duration of the insertion/deletion. For example the following works without glitches:

- (void)addCell
{
    NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForItem:self.numberOfItems
                                                         inSection:0];
    self.numberOfItems++;
    [self.collectionView insertItemsAtIndexPaths:@[indexPathToInsert]];
    [self.collectionView scrollToItemAtIndexPath:indexPathToInsert
                                atScrollPosition:UICollectionViewScrollPositionCenteredVertically
                                        animated:YES];
}

如果在插入的单元格超出当前可见边界时调用上述方法插入单元格,则插入项目时不使用动画,并且集合视图会滚动到它,正确地出列并在途中显示单元格.

If the above method is called to insert a cell while the inserted cell is outside the current visible bounds, the item is inserted without animation and the collection view scrolls to it, properly dequeuing and displaying cells on the way.

问题出现在 iOS 7 &iOS 8 测试版 5.

推荐答案

调整您的内容插入,使其稍微超出设备屏幕尺寸的范围.

Adjust your content insets so that they go beyond the bounds of the device's screen size slightly.

collectionView.contentInsets = UIEdgeInsetsMake(-5,0,0,0); //Adjust this value until it looks ok

这篇关于删除 UICollectionView 边缘的单元格 - 滚动后单元格不会立即出现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆