UICollectionView 动态调整 cell 大小导致不良行为

42

我有一个使用标准UICollectionViewFlowLayout展示单个垂直列单元格的UICollectionViewController。当单击单元格时,我尝试创建一种展开/折叠动画。我使用以下代码来实现此目的:

```swift func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let cell = collectionView.cellForItem(at: indexPath) as! MyCollectionViewCell if cell.isExpanded { UIView.animate(withDuration: 0.5) { cell.expandedView.alpha = 0 } completion: { _ in cell.expandedView.isHidden = true cell.isExpanded = false collectionView.performBatchUpdates(nil, completion: nil) } } else { cell.expandedView.isHidden = false UIView.animate(withDuration: 0.5) { cell.expandedView.alpha = 1 } completion: { _ in cell.isExpanded = true collectionView.performBatchUpdates(nil, completion: nil) } } } ```
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath (NSIndexPath *)indexPath
{
    [self.feedManager setCurrentlySelectedCellIndex:indexPath.item];
    [self.collectionView performBatchUpdates:nil completion:nil];
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //Return the size of each cell to draw
    CGSize cellSize = (CGSize) { .width = 320, .height = [self.feedManager heightForCellAtIndexPath:indexPath] };
    return cellSize;
}
我的管理对象上的“selectedCellIndex”属性告诉数据模型在heightForCellAtIndexpath中返回一个展开或折叠的大小。
[self.collectionView performBatchUpdates:nil completion:nil];

然后 performBatchUpdates:completiong: 方法可以很好地动画化这个大小的变化。但是!当动画发生时,扩展单元格可能会导致屏幕底部的部分可见单元格离开屏幕。

如果是这种情况,然后我将这个单元格折叠,现在离屏的单元格将在没有动画的情况下捕捉到其旧位置,而所有其他可见单元格将像预期的那样进行动画。我的直觉说这是正确的行为,因为当执行折叠动画时,该单元格已经离开了屏幕,���且没有被包含在动画渲染过程中。我的问题是,如何防止这种情况发生?

我希望所有单元格一起动画,无论它们是否在屏幕外。有什么想法吗?


当我在浏览https://dev59.com/JmvXa4cB1Zd3GeqPLKXk时,我发现了一个非常相似的问题。 - Cory Breed
似乎可重用的单元格妨碍了动画,因为屏幕外的单元格被标记为准备重用。有什么想法可以防止单元格被标记为重用吗? - Cory Breed
我有完全相同的问题,真的很想要一个解决方案,即使这个解决方案只是一个临时的数字! - jrturton
3
我建议你自己编写一个布局类。根据我的经验,在处理跨越“layoutAttributesForElementsInRect”边界的单元格时,流式布局动画会出现太多故障。我曾花费几天时间尝试通过重写与动画相关的委托方法来解决这些问题,但最终通过实现自己的布局类获得了更好的效果。 - Timothy Moose
@TimothyMoose,能否在回答中分享一些细节? - jrturton
@jrturton 我开源了我的网格布局,并提供了一个可工作的示例项目(请参见更新后的答案)。 - Timothy Moose
1个回答

29

这是一个UICollectionViewFlowLayout的bug,但有一个解决方法。问题在于initialLayoutAttributesForAppearingItemAtIndexPath:返回的属性具有错误的框架。具体来说,它们具有最终在屏幕上位置的框架,而不是初始的离屏位置的框架。您只需要覆盖此方法并返回正确的框架即可。覆盖的基本结构如下所示:

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    UICollectionViewLayoutAttributes *pose = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
    if (<test for incorrect frame>) {
        CGRect frame = pose.frame;
        frame.origin.y = <calculate correct frame>;
        pose.frame = frame;
    }
    return pose;
}
你需要负责识别需要调整帧的情况,这可能涉及保持自己的内部状态。我还没有完全解决这个逻辑问题,但我做了一个简单的测试,并且能够实现平滑动画。
总体来说,我的经验是UICollectionViewFlowLayout非常有bug,在将移动物品与任何插入、删除或移动结合在一起时,有许多角落情况需要处理,所以我发现编写自己的简单布局更容易。如果你不打算进行任何插入、删除或移动操作,则重写UICollectionViewFlowLayout可能是最好的选择。
如果需要更多帮助,请告诉我。
编辑:
如果您想查看第三方布局,请查看我开源的自定义网格布局VCollectionViewGridLayout,并添加了演示平滑展开单元格高度的示例项目。尝试运行Expand project。还有一个包含动画排序和过滤的Sort & Filter project。这两个项目都允许您在流布局和网格布局之间切换,以便您可以看到改进。

你对于 initialLayout... 中错误框架的建议非常正确。我将选中单元格后面的单元格属性存储起来,并将它们与该方法返回的框架进行比较,在必要时进行替换。我的现有布局子类已经做了很多工作,但我会看看你的。 - jrturton
@TimothyMoose 我该如何在我的Xcode项目中包含VCollectionViewLayout和TLIndexPathTools?我刚刚添加了VCollectionViewLayout.h、VCollectionViewLayout.m文件,并将TLIndexPathTools项目文件拖放到我的项目的Frameworks组中,并在Build Phases中将其添加为依赖项和链接框架,但是我收到以下错误:Undefined symbols for architecture i386: "OBJC_CLASS$_NSManagedObject",引用自:objc-class-ref in libTLIndexPathTools.a(TLIndexPathDataModel.o)。 - ftartaggia
谢谢您的询问。我刚意识到我不小心从readme中删除了TLIndexPathTools的安装说明。听起来你已经做了一切,除了链接CoreData和QuartzCore框架。 - Timothy Moose
实际上,我只需将TLIndexPathTools和VCollectionViewGridLayout添加到链接框架中即可构建我的项目。现在,奇怪的是,如果我通过Storyboard将VCollectionViewGridLayout分配给我的CollectionView,一切都很好,但是一旦我添加这行代码:“VCollectionViewGridLayout *layout = [[VCollectionViewGridLayout alloc] init];”,我就会收到错误消息:“Undefined symbols for architecture i386: "OBJC_CLASS$ _NSManagedObject",引用自:libTLIndexPathTools.a(TLIndexPathDataModel.o)中的objc-class-ref。” - ftartaggia
正如你建议的那样,添加CoreData框架似乎解决了这个问题... 我无法弄清楚发生了什么。 - ftartaggia
如果没有CoreData,它将无法运行,因为TLIndexPathSectionInfo类是NSFetchedResultsSectionInfo协议的实现,而TLIndexPathTools的主要组件TLIndexPathDataModel使用此类将数据组织成部分。 - Timothy Moose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接