-->

重新排序UICollectionView细胞(Reorder cells of UICollecti

2019-08-18 07:56发布

考虑一个UICollectionView与流布局和水平方向。 默认情况下,细胞从上到下排列,从左到右。 像这样:

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

在我的情况,收集视图分页和它的设计,使电池的具体数量在每一页适合。 因此,更自然的顺序应该是:

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

什么是最简单的实现这一目标,短期实现自己的自定义布局? 特别是,我不想失去任何免费的午餐与功能的UICollectionViewFlowLayout (如插入/删除动画)。

或者在一般情况下,你如何实现重新排序函数f(n)上的流布局? 这同样可以适用于从右到左的顺序,例如。

我的做法至今

我的第一种方法是继承UICollectionViewFlowLayout和覆盖layoutAttributesForItemAtIndexPath:

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

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

此外,我不得不重写layoutAttributesForElementsInRect:这是布局中使用,以选择要显示的元素的方法。

- (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;
}

在这里,我只是尝试每一个元素,如果它是在给定范围内rect ,我回来了。

如果这种做法是要走的路,我有以下几个比较具体的问题:

  • 有没有什么办法可以简化layoutAttributesForElementsInRect:覆盖,或使其更有效率?
  • 我缺少的东西吗? 在不同的页面的最起码交换细胞产生奇怪的结果。 我怀疑这是关系到initialLayoutAttributesForAppearingItemAtIndexPath:finalLayoutAttributesForDisappearingItemAtIndexPath:但我无法查出究竟是什么问题。
  • 在我的情况下, f(n)取决于列和每一页的行数。 是否有提取该信息的任何方式UICollectionViewFlowLayout ,短硬编码自己的? 我想到的查询layoutAttributesForElementsInRect:以集合视图的边界,并从那里推断的行和列,但这也觉得效率不高。

Answer 1:

我想了很多关于你的问题,来到了以下注意事项:

子类化的FlowLayout似乎是右端和重新排列细胞,并利用流布局动画的最有效途径。 而你的方法工作,除了两个重要的事情:

  1. 比方说,你有只有2个细胞的集合视图,你设计你的网页,以便它可以包含9个细胞。 第一单元将被定位在视图的左上角,像原来的流布局。 您的第二小区,然而,应被定位在图的顶部,它有一个索引路径[0,1]。 经重新排序的索引路径将是[0,3](也将在它的原始位置流布局单元的指数路径)。 而在你layoutAttributesForItemAtIndexPath重写你会发送消息像[super layoutAttributesForItemAtIndexPath:[0, 3]]你会得到一个nil对象,只是因为只有2格:[0,0]和[0,1]。 而这将是你的最后一页的问题。
  2. 即使你可以实现通过重写分页行为targetContentOffsetForProposedContentOffset:withScrollingVelocity:和喜欢手动设置属性itemSizeminimumLineSpacingminimumInteritemSpacing ,它的很多工作,使你的项目是对称的,定义页面边框等。

我thnik,子类流布局正准备给你太多的实现,因为你想要的是不是流布局了。 但是让我们想想在一起。 关于你的问题:

  • layoutAttributesForElementsInRect:覆盖是完全原始的苹果执行如何,所以没有办法简化它。 对于你的情况,虽然,你可以考虑以下几点:如果你有3行每页的项目和项目的第1行帧相交的矩形框,然后(如果所有项目具有相同的尺寸)的第二排和第三排的项目框架此相交矩形。
  • 对不起,我不明白你的第二个问题
  • 在我的情况重新排序功能如下:(a是行的整数 /每一页上都列,行数=列)

F(N)=(N%A²)+(A - 1)(列 - 行)+A²(N /A²); COL =(N%A²)%A; 行=(N%A²)/ A;

回答这个问题时,流布局已经不知道有多少行中的每一列,因为这个号码可以从列依赖于每一个项目的大小有所不同列。 这也可以说一无所知每个页面上的列数,因为它取决于滚动位置,也可以改变。 因此,有没有比查询更好的办法layoutAttributesForElementsInRect ,但是这也将包括细胞,只有partically可见。 因为你的细胞大小相等,理论上你就可以找出有多少行与横向滚动方向您的收藏观点:通过启动迭代每个细胞计数他们和打破,如果他们frame.origin.x变化。

所以,我认为,你有两个选择,以达到你的目的:

  1. 子类UICollectionViewLayout 。 这似乎是实现所有这些方法很多工作,但它是唯一有效的方法。 你可以有像例如性能itemSizeitemsInOneRow 。 然后,你可以很容易地找到一个公式来计算基于它的每个项目的帧的号码(最好的办法是做在prepareLayout和所有帧存储阵列,这样你卡恩访问你所需要的帧layoutAttributesForItemAtIndexPath )。 实施layoutAttributesForItemAtIndexPathlayoutAttributesForItemsInRectcollectionViewContentSize将是非常简单的为好。 在initialLayoutAttributesForAppearingItemAtIndexPathfinalLayoutAttributesForDisappearingItemAtIndexPath你可以在阿尔法属性只是设定为0.0。 这就是流布局动画是如何工作的标准。 通过覆盖targetContentOffsetForProposedContentOffset:withScrollingVelocity:你可以实现的“分页行为”。

  2. 考虑使集合视图与流布局, pagingEnabled = YES ,水平滚动方向和项目尺寸等于屏幕尺寸。 每个屏幕大小的一个项目。 对每一个细胞,你可以设置一个新的集合视图与垂直流布局和相同的数据源和其他收集的意见,但有偏移子视图。 这是非常有效的,因为这样你再用含有9(或其他)单元块,而不是重新使用与标准方法每个单元全收的意见。 所有动画应能正常工作。

在这里你可以下载使用布局子类方法的样本项目。 (#2)



Answer 2:

不会是一个简单的解决方案有2个非标准集合视图UICollectionViewFlowLayout

甚至更好:有一个与水平滚动页面视图控制器,并且每一页将与正常流布局的集合视图。

这个想法是以下几点:在你的UICollectionViewController -init method创建具有框架的第二收集鉴于你原来的集合视图宽度向右偏移。 然后你将其添加为子视图原始集合视图。 为了收集视图之间切换,只需添加一个刷卡识别。 要计算偏移值可以存储在伊娃集合视图中的原始帧cVFrame 。 要确定您的收藏视图,您可以使用标签。

的实施例init方法:

CGRect cVFrame = self.collectionView.frame;
UICollectionView *secondView = [[UICollectionView alloc] 
              initWithFrame:CGRectMake(cVFrame.origin.x + cVFrame.size.width, 0, 
                             cVFrame.size.width, cVFrame.size.height) 
       collectionViewLayout:[UICollectionViewFlowLayout new]];
    [secondView setBackgroundColor:[UIColor greenColor]];
    [secondView setTag:1];
    [secondView setDelegate:self];
    [secondView setDataSource:self];
    [self.collectionView addSubview:secondView];

UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
                      initWithTarget:self action:@selector(swipedRight)];
    [swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];

UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] 
                       initWithTarget:self action:@selector(swipedLeft)];
    [swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];

    [self.collectionView addGestureRecognizer:swipeRight];
    [self.collectionView addGestureRecognizer:swipeLeft];

的实施例swipeRightswipeLeft方法:

-(void)swipedRight {
    // Switch to left collection view
    [self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
}

-(void)swipedLeft {
    // Switch to right collection view
    [self.collectionView setContentOffset:CGPointMake(cVFrame.size.width, 0) 
                                 animated:YES];
}

然后它不是实现数据源的方法(在你的情况,你想拥有的每一页上的9个项目)的一个大问题:

-(NSInteger)collectionView:(UICollectionView *)collectionView 
    numberOfItemsInSection:(NSInteger)section {
    if (collectionView.tag == 1) {
         // Second collection view
         return self.dataArray.count % 9;
    } else {
         // Original collection view
         return 9; // Or whatever
}

在方法-collectionView:cellForRowAtIndexPath你将需要与偏移从模型得到的数据,如果它的第二个集合视图。

也不要忘记为你的第二个集合视图可重复使用的电池注册类为好。 您也可以只创建一个手势识别器和识别刷卡的左侧和右侧。 由你决定。

我想,现在它应该工作,尝试一下:-)



Answer 3:

您有实现目标UICollectionViewDataSource协议。 里面collectionView:cellForItemAtIndexPath:简单地返回要返回正确的项目。 我不明白的地方就不会有问题。

编辑:好吧,我看到的问题。 这里是解决方案: http://www.skeuo.com/uicollectionview-custom-layout-tutorial ,具体步骤17到25这不是一个巨大的工作量,并且可以很容易被重用。



Answer 4:

这是我的解决方案,我没有与动画进行测试,但我认为它会好一点点变化而努力,希望有帮助

CELLS_PER_ROW = 4;
CELLS_PER_COLUMN = 5;
CELLS_PER_PAGE = CELLS_PER_ROW * CELLS_PER_COLUMN;

UICollectionViewFlowLayout* flowLayout = (UICollectionViewFlowLayout*)collectionView.collectionViewLayout;
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.itemSize = new CGSize (size.width / CELLS_PER_ROW - delta, size.height / CELLS_PER_COLUMN - delta);
flowLayout.minimumInteritemSpacing = ..;
flowLayout.minimumLineSpacing = ..;
..

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger index = indexPath.row;
    NSInteger page = index / CELLS_PER_PAGE;
    NSInteger indexInPage = index - page * CELLS_PER_PAGE;
    NSInteger row = indexInPage % CELLS_PER_COLUMN;
    NSInteger column = indexInPage / CELLS_PER_COLUMN;

    NSInteger dataIndex = row * CELLS_PER_ROW + column + page * CELLS_PER_PAGE;

    id cellData = _data[dataIndex];
    ...
}


文章来源: Reorder cells of UICollectionView