【问题标题】:Animated scroll-to-item in UICollectionView doesn't always workUICollectionView 中的动画滚动到项目并不总是有效
【发布时间】:2013-07-19 02:05:31
【问题描述】:

问题

我想让 UICollectionView 对特定项目进行动画滚动。

这在大多数情况下都有效,但有时我试图滚动到的项目最终不会显示

代码

- (void)onClick {
  // (Possibly recompute the _items array.)
  NSInteger target_idx = // (...some valid index of _items)
  NSIndexPath *item_idx = [NSIndexPath indexPathForItem:target_idx inSection:0];
  [self scrollToItem:item_idx];
}

- (void)scrollToItem:(NSIndexPath*)item_idx {
  // Make sure our view is up-to-date with the data we want to show.
  NSInteger num_items = [self.collection_view numberOfItemsInSection:0];
  if (num_items != _items.count) {
    [self.collection_view reloadData];
  }

  [self.collection_view 
    scrollToItemAtIndexPath:item_idx
           atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
                   animated:YES];
}

详情

  • self.collection_view 是一个 UICollectionView,由单行项目组成,具有标准流布局和启用水平滚动。
  • 我需要在滚动之前调用reloadData,因为自 UICollectionView 上次显示之后_items 可能已经发生了变化。
  • 这个问题只发生在动画滚动上;如果我通过 animated:NO,那么一切都会按预期进行。
  • 当问题发生时,对 indexPathsForVisibleItems 的后滚动调用显示 UICollectionView 不认为目标项可见

任何想法为什么有时会默默地滚动到某个项目失败?


更新:问题似乎来自快速连续的重新加载和滚动;如果没有重新加载,滚动将按预期进行。加载新数据后滚动到某个项目是否有任何成语?

【问题讨论】:

  • 我的猜测是 reloadData 调用给你搞砸了。如果你把它注释掉会发生什么?
  • @NicholasHart:这是个好主意,但问题是我的数据经常更改(这意味着我需要reloadData),因此我想滚动到一个新项目(因为我有新数据)。如果我不重新加载,我可能会滚动到无效索引,可能会导致崩溃。重新加载后滚动似乎是一种应该相当普遍的模式,但也许不是?
  • 我并不是建议您不要重新加载作为一个行动方案,我只是询问暂时删除它是否会导致不良行为停止。如果是这样,您知道该采取哪个方向来寻找解决方案(例如:找出一种方法来等到表格完成重新加载后再滚动。)
  • 如果我注释掉reloadData,滚动似乎可以正常工作。所以问题就变成了:我们如何将重新加载与滚动结合起来?我尝试重新加载,异步等待,然后滚动,但问题仍然存在。也许我错过了一些invalidate 电话?
  • 好吧,我不认为 UICollectionViewDelegate 或 UICollectionViewDataSource 有任何方法可以明确告诉您“重新加载完成”,所以这可能是一个难以解决的问题。我有点想知道在处理“点击”时重新加载。您的数据更新情况如何?也许您应该在数据更新时重新加载视图,而不是在处理用户输入时重新加载。

标签: ios objective-c scroll uicollectionview animated


【解决方案1】:

在将项目添加到UICollectionView 后,我刚刚遇到了类似/相同的问题。

发生了什么

问题似乎是紧接在[collectionView reloadData][collectionView insertItemsAtIndexPaths: @[newItemIndexPath]] 之后,集合视图的内容大小尚未更新

如果您随后尝试滚动可见的添加项目,它将失败,因为内容矩形尚未包含新项目的空间

修复

有一个简单且相当健壮的解决方法,即将滚动事件发布到运行循环的下一次迭代,如下所示:

const NSUInteger newIndex = 
  [self collectionView: self.collectionView numberOfItemsInSection: 0] - 1;

NSIndexPath *const newPath = 
  [NSIndexPath indexPathForItem: newIndex
                      inSection: 0];

[self.collectionView insertItemsAtIndexPaths: @[newPath]];

UICollectionViewLayoutAttributes *const layoutAttributes =
  [self.collectionView layoutAttributesForItemAtIndexPath: newPath];

dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView scrollRectToVisible: layoutAttributes.frame 
                                    animated: YES];
});

没有更好的解决办法吗?

虽然它有效,但这种“在下一个运行循环滴答声上发布滚动调用”的恶作剧感觉很老套。

如果UICollectionView 可以在完成更新内容矩形时调用回调,那就更好了。 UIKit 为其他对其模型执行异步更新的方法提供了这种风格的回调。例如,UIViewController 过渡和 UIView 动画的完成块。

UICollectionView 不提供此回调。据我所知,当它完成更新时,没有其他简单的干净方法可以找到。如果没有这个,下一个运行循环滴答是我们更喜欢使用的回调独角兽的可行马代理。

还有什么要知道的吗?

知道UITableView 也有这个问题可能很有用。类似的解决方法也应该在那里工作。

【讨论】:

    【解决方案2】:

    在@NicholasHart 的帮助下,我想我理解了这个问题。

    尝试reloadData 然后执行动画滚动到新位置是有意义的(并且似乎有效),只要重新加载使集合更大

    当重新加载 收缩 集合时,动画滚动的起点可能不再存在。这让动画很麻烦。

    例如,如果您从一直向右滚动的视图开始(以便您最右边的项目可见),然后重新加载并丢失一半的项目,则不清楚动画的起点应该是什么.在这种情况下尝试进行动画滚动会导致无操作或跳转到一个非常奇怪的位置。

    一种看起来相当不错的解决方案是仅在集合变大时才进行动画处理:

    - (void)scrollToItem:(NSIndexPath*)item_idx {
      // Make sure our view is up-to-date with the data we want to show.
      NSInteger old_num_items = [self.collection_view numberOfItemsInSection:0];
      NSInteger new_num_items = _items.count;
      if (old_num_items != new_num_items) {
        [self.collection_view reloadData];
      }
    
      // Animating if we're getting smaller doesn't really make sense, and doesn't 
      // seem to be supported.
      BOOL is_expanding = new_num_items >= old_num_items;
      [self.collection_view 
        scrollToItemAtIndexPath:item_idx
               atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
                       animated:is_expanding];
    }
    

    【讨论】:

    • 在此代码中滚动工作但滚动视图指示器不显示且滚动不流畅
    【解决方案3】:

    这对我有用:

    [UIView performWithoutAnimation:^{
        [self.collectionView performBatchUpdates:^{
            [self.collectionView reloadData];
        } completion:^(BOOL finished) {
            if (finished) {
                [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:messages.count - 1 inSection:0]
                                            atScrollPosition:UICollectionViewScrollPositionBottom
                                                    animated:NO];
            }
        }];
    }];
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-26
      • 2013-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-30
      • 1970-01-01
      相关资源
      最近更新 更多