重新排序UICollectionView的单元格 [英] Reorder cells of UICollectionView

查看:77
本文介绍了重新排序UICollectionView的单元格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑具有流布局和水平方向的 UICollectionView 。默认情况下,单元格从上到下,从左到右排序。像这样:

Consider a UICollectionView with flow layout and horizontal direction. By default, cells are ordered from top to bottom, left to right. Like this:

1 4 7 10 13 16
2 5 8 11 14 17
3 6 9 12 15 18

在我的情况下,集合视图是分页的,它的设计是为了特定的每页的细胞数量。因此,更自然的顺序是:

In my case, the collection view is paged and it has been designed so that a specific number of cells fits in each page. Thus, a more natural ordering would be:

1 2 3   10 11 12
4 5 6 - 13 14 15
7 8 9   16 17 18

最简单的方法是什么,实现我自己的自定义布局?特别是,我不想放弃使用 UICollectionViewFlowLayout 免费提供的任何功能(例如插入/删除动画)。

What would be the simplest to achieve this, short of implementing my own custom layout? In particular, I don't want to loose any of the functionalities that come for free with UICollectionViewFlowLayout (such as insert/remove animations).

或者一般来说,如何在流布局上实现重新排序函数 f(n)?例如,同样可能适用于从右到左的排序。

Or in general, how do you implement a reordering function f(n) on a flow layout? The same could be applicable to a right-to-left ordering, for example.

我的第一种方法是子类 UICollectionViewFlowLayout 并覆盖 layoutAttributesForItemAtIndexPath:

My first approach was to subclass UICollectionViewFlowLayout and override layoutAttributesForItemAtIndexPath::

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSIndexPath *reorderedIndexPath = [self reorderedIndexPathOfIndexPath:indexPath];
    UICollectionViewLayoutAttributes *layout = [super layoutAttributesForItemAtIndexPath:reorderedIndexPath];
    layout.indexPath = indexPath;
    return layout;
}

其中 reorderedIndexPathOfIndexPath: f(n)。通过调用 super ,我不必手动计算每个元素的布局。

Where reorderedIndexPathOfIndexPath: is f(n). By calling super, I don't have to calculate the layout of each element manually.

此外,我必须覆盖 layoutAttributesForElementsInRect:,这是布局用于选择要显示的元素的方法。

Additionally, I had to override layoutAttributesForElementsInRect:, which is the method the layout uses to choose which elements to display.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *result = [NSMutableArray array];
    NSInteger sectionCount = 1;
    if ([self.collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
    {
        sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
    }
    for (int s = 0; s < sectionCount; s++)
    {
        NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
        for (int i = 0; i < itemCount; i++)
        {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
            UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
            if (CGRectIntersectsRect(rect, layout.frame))
            {
                [result addObject:layout];
            }
        }
    }
    return result;
}

这里我只尝试每个元素,如果它在给定的<$ c内$ c> rect ,我将其退回。

Here I just try every element and if it is within the given rect, I return it.

如果采用这种方法,我有以下更具体的问题:

If this approach is the way to go, I have the following more specific questions:


  • 有什么方法可以简化 layoutAttributesForElementsInRect:覆盖,或者提高效率?

  • 我错过了什么吗?至少交换不同页面的单元格会产生奇怪的结果。我怀疑它与 initialLayoutAttributesForAppearingItemAtIndexPath: finalLayoutAttributesForDisappearingItemAtIndexPath:有关,但我无法准确指出问题所在。

  • 就我而言, f(n)取决于每页的列数和行数。有没有办法从 UICollectionViewFlowLayout 中提取这些信息,还没有自己硬编码?我想到了使用集合视图的边界查询 layoutAttributesForElementsInRect: ,并从那里推断出行和列,但这也感觉效率低下。

  • Is there any way I can simplify the layoutAttributesForElementsInRect: override, or make it more efficient?
  • Am I missing something? At the very least swapping cells of different pages produces odd results. I suspect it's related to initialLayoutAttributesForAppearingItemAtIndexPath: and finalLayoutAttributesForDisappearingItemAtIndexPath:, but I can't pinpoint exactly what is the problem.
  • In my case, f(n) depends on the number of columns and rows of each page. Is there any way of extracting this information from UICollectionViewFlowLayout, short of hardcoding it myself? I thought of querying layoutAttributesForElementsInRect: with the bounds of the collection view, and deducing the rows and columns from there, but this also feels inefficient.

推荐答案

我我们已经考虑了很多关于你的问题,并考虑了以下因素:

I've thought a lot about your question and came to following considerations:

对FlowLayout进行子类化似乎是重新排序细胞和利用的最有效和最有效的方法流动布局动画。除了两个重要的事情之外,你的方法是有效的:

Subclassing the FlowLayout seems to be the rightest and the most effective way to reorder cells and to make use of flow layout animations. And your approach works, except of two important things:


  1. 假设你有一个只有2个单元格的集合视图,你设计了你的页面这样它就可以包含9个细胞。第一个单元格将位于视图的左上角,就像原始流程布局一样。但是,您的第二个单元格应位于视图的顶部,并且它具有索引路径[0,1]。重新排序的索引路径将是[0,3](原始流布局单元的索引路径将在其位置上)。在你的 layoutAttributesForItemAtIndexPath 覆盖中,你会发送消息,如 [super layoutAttributesForItemAtIndexPath:[0,3]] ,你会得到 nil 对象,因为只有2个单元:[0,0]和[0,1]。这将是你最后一页的问题。

  2. 即使你可以通过覆盖 targetContentOffsetForProposedContentOffset:withScrollingVelocity:来实现分页行为,手动设置属性,如 itemSize minimumLineSpacing minimumInteritemSpacing ,它是为了使你的项目对称,定义分页边框等等,我们做了很多工作。

  1. Let's say you have a collection view with only 2 cells and you have designed your page so that it can contain 9 cells. First cell will be positioned at the top left corner of the view, like in original flow layout. Your second cell, however, should be positioned at the top of the view and it has an index path [0, 1]. The reordered index path would be [0, 3] (index path of original flow layout cell that would be on its place). And in your layoutAttributesForItemAtIndexPath override you would send the message like [super layoutAttributesForItemAtIndexPath:[0, 3]], you would get an nil object, just because there are only 2 cells: [0,0] and [0,1]. And this would be the problem for your last page.
  2. Even though you can implement the paging behavior by overriding targetContentOffsetForProposedContentOffset:withScrollingVelocity: and manually set properties like itemSize, minimumLineSpacing and minimumInteritemSpacing, it's much work to make your items be symmetrical, to define the paging borders and so on.

我是thnik,对流程布局进行子类化准备工作正在准备中很多实现,因为你想要的不再是流程布局了。但让我们一起思考它。
关于你的问题:

I thnik, subclassing the flow layout is preparing much implementation for you, because what you want is not a flow layout anymore. But let's think about it together. Regarding your questions:


  • 你的 layoutAttributesForElementsInRect:覆盖正是如何原来的苹果实现是,所以没有办法简化它。但是,对于您的情况,您可以考虑以下内容:如果每页有3行项目,并且第一行中的项目框架与矩形框架相交,则(如果所有项目具有相同大小)第二行和第三行项目的框架这个矩形相交。

  • 对不起,我不明白你的第二个问题

  • 在我的情况下,重新排序功能如下所示:(a是整数每页上的行/列数,行=列)

  • your layoutAttributesForElementsInRect: override is exactly how the original apple implementation is, so there is no way to simplify it. For your case though, you could consider following: if you have 3 rows of items per page, and the frame of item in first row intersects the rect frame, then (if all items have same size) the frames of second and third row items intersect this rect.
  • sorry, I didn't understand your second question
  • in my case the reordering function looks like this: (a is the integer number of rows/columns on every page, rows=columns)

f(n)=(n%a²)+(a - 1)(col - row)+a²(n /a²); col =(n%a²)%a; row =(n%a²)/ a;

f(n) = (n % a²) + (a - 1)(col - row) + a²(n / a²); col = (n % a²) % a; row = (n % a²) / a;

回答问题时,流程布局不知道每列中有多少行因为这个数字可能因列而异,具体取决于每个项目的大小。它也可以说每页上的列数,因为它取决于滚动位置,也可以变化。因此,没有比查询 layoutAttributesForElementsInRect 更好的方法,但这也包括仅仅是部分可见的单元格。由于您的单元格大小相等,理论上您可以通过水平滚动方向找出集合视图中有多少行:通过开始迭代计算它们的每个单元格并打破它们的 frame.origin.x 更改。

Answering the question, the flow layout has no idea how many rows are in each column because this number can vary from column to column depending on size of every item. It can also say nothing about number of columns on each page because it depends on the scrolling position and can also vary. So there is no better way than querying layoutAttributesForElementsInRect, but this will include also cells, that are only partically visible. Since your cells are equal in size, you could theoretically find out how many rows has your collection view with horizontal scrolling direction: by starting iterating each cell counting them and breaking if their frame.origin.x changes.

因此,我认为,您有两种方法可以达到目的:


  1. 子类 UICollectionViewLayout 。实现所有这些方法似乎很多工作,但这是唯一有效的方法。例如,您可以使用 itemSize itemsInOneRow 等属性。然后你可以很容易地找到一个公式来根据它的数量来计算每个项目的框架(最好的方法是在 prepareLayout 中进行,并将所有框架存储在数组中,这样你可以在 layoutAttributesForItemAtIndexPath 中访问你需要的框架。实现 layoutAttributesForItemAtIndexPath layoutAttributesForItemsInRect collectionViewContentSize 将非常简单好。在 initialLayoutAttributesForAppearingItemAtIndexPath finalLayoutAttributesForDisappearingItemAtIndexPath 中,您可以将alpha属性设置为0.0。这就是标准流布局动画的工作原理。通过覆盖 targetContentOffsetForProposedContentOffset:withScrollingVelocity:,您可以实现分页行为。

  1. Subclass UICollectionViewLayout. It seems to be much work implementing all those methods, but it's the only effective way. You could have for example properties like itemSize, itemsInOneRow. Then you could easily find a formula to calculate the frame of each item based on it's number (the best way is to do it in prepareLayout and store all frames in array, so that you cann access the frame you need in layoutAttributesForItemAtIndexPath). Implementing layoutAttributesForItemAtIndexPath, layoutAttributesForItemsInRect and collectionViewContentSize would be very simple as well. In initialLayoutAttributesForAppearingItemAtIndexPath and finalLayoutAttributesForDisappearingItemAtIndexPath you could just set the alpha attribute to 0.0. That's how standard flow layout animations work. By overriding targetContentOffsetForProposedContentOffset:withScrollingVelocity: you could implement the "paging behavior".

考虑制作一个集合使用流布局查看, pagingEnabled = YES ,水平滚动方向和项目大小等于屏幕大小。每个屏幕尺寸一个项目。对于每个单元格,您可以将新的集合视图设置为具有垂直流布局的子视图和与其他集合视图相同但具有偏移的相同数据源。它非常有效,因为您可以重用包含9个(或其他)单元格块的整个集合视图,而不是使用标准方法重用每个单元格。所有动画都应该正常工作。

Consider making a collection view with flow layout, pagingEnabled = YES, horizontal scrolling direction and item size equal to screen size. One item per screen size. To each cell you could set a new collection view as subview with vertical flow layout and the same data source as other collection views but with an offset. It's very efficient, because then you reuse whole collection views containing blocks of 9 (or whatever) cells instead of reusing each cell with standard approach. All animations should work properly.

这里您可以使用布局子类化方法下载示例项目。 (#2)

Here you can download a sample project using the layout subclassing approach. (#2)

这篇关于重新排序UICollectionView的单元格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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