重新sortingUICollectionView的单元格

考虑具有stream布局和水平方向的UICollectionView 。 默认情况下,单元格从上到下,从左到右排列。 喜欢这个:

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

在我的情况下,集合视图是分页的,它的devise使每个页面都有特定数量的单元格。 因此,更自然的sorting是:

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

最简单的做法是实现我自己的自定义布局吗? 特别是,我不想放弃使用UICollectionViewFlowLayout免费提供的任何function(例如插入/移除animation)。

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

我的方法到目前为止

我的第一个方法是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:这是布局用来select要显示的元素的方法。

 - (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:重写,还是使其更有效率?
  • 我错过了什么吗? 至less在不同的页面交换单元会产生奇怪的结果。 我怀疑这是关系到initialLayoutAttributesForAppearingItemAtIndexPath:finalLayoutAttributesForDisappearingItemAtIndexPath:但我不能精确定位到底是什么问题。
  • 在我的情况下, f(n)取决于每个页面的列数和行数。 有没有什么办法从UICollectionViewFlowLayout中提取这个信息,缺乏自己的硬编码? 我想查询layoutAttributesForElementsInRect:与集合视图的边界,并从那里推断行和列,但这也感觉效率低下。

我想了很多关于你的问题,并提出以下考虑:

对FlowLayout进行子类化似乎是重新sorting单元格和使用stream布局animation的最正确和最有效的方法。 你的方法是有效的,除了两件重要的事情:

  1. 假设你有一个只有2个单元格的集合视图,并且你devise了你的页面,以便它可以包含9个单元格。 第一个单元格将位于视图的左上angular,就像原始stream布局一样。 然而,你的第二个单元格应该放在视图的顶部,它有一个索引path[0,1]。 重新sorting的索引path将是[0,3](原始stream布局单元的索引path将在其位置上)。 在你的layoutAttributesForItemAtIndexPath覆盖中,你会发送像[super layoutAttributesForItemAtIndexPath:[0, 3]]这样的消息,你将得到一个nil对象,仅仅因为只有两个单元:[0,0]和[0,1]。 这将是你最后一页的问题。
  2. 即使您可以通过覆盖targetContentOffsetForProposedContentOffset:withScrollingVelocity:来实现分页行为targetContentOffsetForProposedContentOffset:withScrollingVelocity:并且手动设置属性(如itemSizeminimumLineSpacingminimumInteritemSpacing ,但是使项目对称,定义分页边框等工作量很大。

我thnik,inheritancestream布局正在为你准备很多实现,因为你想要的不是一个stream布局了。 但让我们一起思考。 关于你的问题:

  • 你的layoutAttributesForElementsInRect:覆盖正是原来的苹果实现,所以没有办法简化它。 对于你的情况,你可以考虑如下:如果每页有3行项目,并且第一行项目的框架与矩形框架相交,则(如果所有项目具有相同的大小)第二和第三行项目的框架相交这个矩形。
  • 对不起,我不明白你的第二个问题
  • 在我的情况下,重新sorting函数如下所示:(a是每个页面上的行/列的整数 ,rows = columns)

f(n)=(n%a²)+(a – 1)(col – row)+a²(n /a²); col =(n%a²)%a; 行=(n%a 2)/ a;

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

所以,我认为,你有两个select来达到你的目的:

  1. 子类UICollectionViewLayout 。 实施所有这些方法似乎有很多工作,但这是唯一有效的方法。 你可以有例如itemSizeitemsInOneRow等属性。 然后你可以很容易地find一个公式来计算每个项目的帧的基础上它的数字(最好的方法是在prepareLayout并将所有帧存储在数组中,以便您无法访问您在layoutAttributesForItemAtIndexPath所需的帧)。 实现layoutAttributesForItemAtIndexPathlayoutAttributesForItemsInRectcollectionViewContentSize也是非常简单的。 在initialLayoutAttributesForAppearingItemAtIndexPathfinalLayoutAttributesForDisappearingItemAtIndexPath您可以将alpha属性设置为0.0。 这就是标准stream布局animation的工作原理。 通过覆盖targetContentOffsetForProposedContentOffset:withScrollingVelocity:你可以实现“分页行为”。

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

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

这不是一个简单的解决scheme,有2个集合视图与标准的UICollectionViewFlowLayout

或者甚至更好:具有水平滚动的页面视图控制器,并且每个页面将是具有正常stream布局的集合视图。

这个想法如下:在你的UICollectionViewController -init method中,通过原始的集合视图宽度创build第二个右边框架偏移的集合视图。 然后将其作为子视图添加到原始集合视图。 要在集合视图之间切换,只需添加一个滑动识别器即可。 要计算偏移值,您可以将采集视图的原始帧存储在ivar cVFrame 。 要标识您的collections视图,您可以使用标签。

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

然后,实现DataSource方法不是一个大问题(在你的情况下你想在每个页面上有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 ,如果它是第二个集合视图,则需要从模型中获取具有偏移量的数据。

另外,不要忘了为第二个集合视图注册可重用单元的类。 您也可以只创build一个手势识别器,并识别左右滑动。 随你便。

我想,现在它应该工作,试试看:-)

你有一个实现UICollectionViewDataSource协议的对象。 里面的collectionView:cellForItemAtIndexPath:只是返回你想要返回正确的项目。 我不明白哪里会有问题。

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

这是我的解决scheme,我没有animationtesting,但我认为这将是一个小的变化,并希望它有帮助

 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]; ... }