UICollectionViewScrollDirectionHorizo​​ntal:如何将顺序从上下更改为左右

Posted

技术标签:

【中文标题】UICollectionViewScrollDirectionHorizo​​ntal:如何将顺序从上下更改为左右【英文标题】: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

【讨论】:

以上是关于UICollectionViewScrollDirectionHorizo​​ntal:如何将顺序从上下更改为左右的主要内容,如果未能解决你的问题,请参考以下文章