【问题标题】:UICollectionViewScrollDirectionHorizontal: how to change order from top-bottom to left-rightUICollectionViewScrollDirectionHorizo​​ntal:如何将顺序从上下更改为左右
【发布时间】: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 不提供改变单元格定位行为的选项呢?

顺便说一句,我想保持行数不变...

【问题讨论】:

    标签: ios uicollectionview uicollectionviewcell uicollectionviewlayout


    【解决方案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】:

      关于上述滚动问题,我找到了以下解决方案:

      1. 计算行的最大宽度(在layoutAttributesForElementsInRect中),
      2. 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

      【讨论】:

        猜你喜欢
        • 2018-07-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-03
        • 1970-01-01
        • 2011-04-18
        • 2023-03-13
        • 2020-02-07
        • 2018-11-07
        相关资源
        最近更新 更多