UICollectionViewScrollDirectionHorizontal:如何将顺序从上下更改为左右
Posted
技术标签:
【中文标题】UICollectionViewScrollDirectionHorizontal:如何将顺序从上下更改为左右【英文标题】:UICollectionViewScrollDirectionHorizontal: how to change order from top-bottom to left-right 【发布时间】:2015-10-16 19:00:22 【问题描述】:我正在实现一个带有流布局的集合视图。用户可以在水平和垂直滚动方向之间切换。
无论如何: 当它处理水平方向时,表格视图中的元素从上到下定位,如下所示:
1 4 7 2 5 8 3 6 9
不过,我想把顺序从左到右改一下,即:
1 2 3 4 5 6 7 8 9
我的研究没有带来预期的结果——另外,集合视图和流布局的 API 没有提供允许用户翻转列和行的标志。
有人找到解决办法了吗? 我想知道显然还没有讨论过这样的问题。 此外,大多数人是从左到右而不是从上到下阅读 - 那么为什么 Apple 不提供改变单元格定位行为的选项呢?
顺便说一句,我想保持行数不变...
【问题讨论】:
【参考方案1】:我生性厚颜无耻,我可以自己弄清楚该怎么做(是的,这拯救了我的一天):
首先,我继承了 UICollectionViewFlowLayout(我的子类的名称是 CollectionViewFlowLayout)。
然后我覆盖方法 prepareLayout,在该方法中我用所有元素初始化缓存(带有 UICollectionViewLayoutAttributes 的可变数组),这些元素是我从集合视图中获取的 -> 所以丢失了滚动导致的元素不再是问题!
最后我覆盖了 layoutAttributesForElementsInRect,在此我再次提取所有 UICollectionViewLayoutAttributes,覆盖缓存中每个元素的几何位置(=帧)。当我从缓存(数组)中获取所有元素时,与
相反 NSMutableArray * attributesArray = [[super layoutAttributesForElementsInRect: rect] mutableCopy];
我可以处理所有单元格 - 不仅是可见的单元格!
缓存还有一个额外的好处:元素必须在布局失效后才更新 - 因此滚动更加流畅,没有闪烁。
这是我的源代码:
@interface CollectionViewFlowLayout()
// cache for all collection view cell's layouts
NSMutableArray * cache;
// flag indicating that attributes can be read from cache (performance issue)
bool attributesHaveBeenUpdated;
// number of all cells in the collection view
int numberOfTotalCells;
@end
@implementation CollectionViewFlowLayout
/**
*
* Is called whenever the collection view’s layout is invalidated,
* for example after device rotation
*
**/
- (void)prepareLayout
attributesHaveBeenUpdated = false;
if (!cache)
cache = [NSMutableArray new];
if (cache.count == 0)
numberOfTotalCells = (int)[self.collectionView numberOfItemsInSection:0]; //(int) attributesArray.count; //
// now populate the cache with ALL cells of collection view (not only the visibe ones)
for (int i=0; i<numberOfTotalCells; i++)
UICollectionViewLayoutAttributes * attributes = [super layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
[cache addObject:attributes];
/**
*
* Is called whenever cells appear/disappear after scrolling
* and after "prepareLayout"
*
**/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
NSMutableArray * attributesArray = [[super layoutAttributesForElementsInRect: rect] mutableCopy]; // reflects only the attributes of visible cells within the scroll area!
if (self.scrollDirection == UICollectionViewScrollDirectionVertical || !cache)
return attributesArray; // nothing to do ... the order of the elements is ok
if (attributesHaveBeenUpdated)
// nothing to do, just take the elements from cache!
return cache;
// height of collection view
float availableHeight = self.collectionViewContentSize.height;
// height of a single collection view cell (here: equal sizes!)
float cellHeight = ((UICollectionViewLayoutAttributes*)attributesArray[0]).frame.size.height;
// height of a single collection view cell (here: equal sizes!)
float cellWidth = ((UICollectionViewLayoutAttributes*)attributesArray[0]).frame.size.width;
// minimal spacing between two rows -> to be multiplied later with (numberOfRows - 1)
float minLineSpacing = self.minimumLineSpacing;
// minimal spacing between two columns
float minInteritemSpacing = self.minimumInteritemSpacing;
// now calculate the maximal number of rows in function of cellHeight,
// minLineSpacing and availableHeight -> formula comes from:
// availableHeight = numberOfRows*cellHeight + (numberOfRows-1)*minLineSpacing
int numberOfRows = floor((availableHeight + minLineSpacing) / (cellHeight + minLineSpacing));
/**
*
* Now we need to calculate the new positioning of the cells in function of
* the maximal number of possible rows (=numberOfRows). We start with the
* first row and try to achieve the best reapartition of cells in each row,
* so that rows 1...N-1 are filled with the same number of cells (=n). The last
* row should be filled with the remaining elements -> number = between 1 and n.
*
**/
// get the number of cells until the penultimate row
int numberOfCellsPerMainRow = ceil((float)numberOfTotalCells / (float)numberOfRows);
// number of total cells from row 1...N-1
int numberOfMainRowCells = numberOfCellsPerMainRow*(numberOfRows-1);
// number of cells in last row
int numberOfLastRowCells = numberOfTotalCells - numberOfMainRowCells;
// special case : if no remaining element for last row, decrease the number of cells
// in previous rows -> remember: we want to fill each available row!
if (numberOfLastRowCells == 0)
numberOfCellsPerMainRow--;
numberOfMainRowCells = numberOfCellsPerMainRow*(numberOfRows-1);
numberOfLastRowCells += numberOfRows;
for (int i=0; i<numberOfTotalCells; i++)
// now overwrite the geometrical properties of each cell
UICollectionViewLayoutAttributes* attr = cache[i];// get from cache ...
int row = floor((float)i/(float)numberOfCellsPerMainRow);
int col = i % numberOfCellsPerMainRow;
// overwrite layout in the cache ("call by reference")
attr.frame = CGRectMake(col*(cellWidth+minInteritemSpacing), row*(cellHeight+minLineSpacing), cellWidth, cellHeight);
attributesHaveBeenUpdated = true; // the next time, read from cache!
return cache;
// always return YES
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
return YES;
@end
这种方法效果很好 - 只有一个小问题: 当我向右滚动时,最后一个元素被截断 - 因此仅部分显示。也许集合视图内容大小仍然是个问题?
【讨论】:
【参考方案2】:关于上述滚动问题,我找到了以下解决方案:
-
计算行的最大宽度(在layoutAttributesForElementsInRect中),
在 UICollectionViewFlowLayout 的子类中覆盖 collectionViewContentSize。
- (CGSize)collectionViewContentSize
return CGSizeMake(maxRowWidth, self.collectionView.frame.size.height);
在 layoutAttributesForElementsInRect(见上文)
// get the width of the largest row (we need it for increasing the scroll area)
maxRowWidth = numberOfCellsPerMainRow * cellWidth + (numberOfCellsPerMainRow - 1) * minInteritemSpacing;
现在一切正常!
Here's a GIF of my final collection view
【讨论】:
以上是关于UICollectionViewScrollDirectionHorizontal:如何将顺序从上下更改为左右的主要内容,如果未能解决你的问题,请参考以下文章