以相反的顺序填充 UICollectionView

Populating UICollectionView in reverse order

我想以相反的顺序填充 UICollectionView,以便首先填充 UICollectionView 的最后一项,然后填充倒数第二项,依此类推。实际上我正在应用动画并且项目正在一个接一个地出现。因此,我希望最后一项首先出现。

我假设您正在使用 UICollectionViewFlawLayout,而这没有执行该操作的逻辑,它只能以 TOP-LEFT BOTTOM-RIGHT 的顺序工作。为此,您必须构建自己的布局,您可以创建一个继承自 UICollectionViewLayout 的新对象。

看起来工作量很大,但实际上并没有那么多,你必须实现4个方法,而且由于你的布局只是自下而上应该很容易知道每个单元格的框架。

在这里查看苹果教程:https://developer.apple.com/library/prerelease/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html

不知道这是否还有用,但我想它可能对其他人很有用。

如果您的集合视图的单元格具有相同的高度,那么对于您的问题,实际上有一个比构建自定义 UICollectionViewLayout 更简单的解决方案。

首先,只需创建集合视图顶部约束的出口并将此代码添加到视图控制器:

-(void)viewWillAppear:(BOOL)animated {
    [self.view layoutIfNeeded]; //for letting the compiler know the actual height and width of your collection view before we start to operate with it
    if (self.collectionView.frame.size.height > self.collectionView.collectionViewLayout.collectionViewContentSize.height) {
        self.collectionViewTopConstraint.constant = self.collectionView.frame.size.height - self.collectionView.collectionViewLayout.collectionViewContentSize.height;
    }

所以基本上只有当视图的高度较大时,您才计算集合视图的高度与其内容之间的差异。然后将其调整为约束常数。很简单。但是,如果您还需要实现单元格大小调整,则此代码还不够。但我想这种方法可能非常有用。希望这有帮助。

令我惊讶的是,Apple 吓得人们不敢在文档中编写自己的 UICollectionViewLayout。这真的非常简单。这是我刚刚在应用程序中使用的一个实现,它可以完全满足要求。新项目出现在底部,虽然没有足够的内容填满屏幕,但项目底部对齐,就像您在消息应用程序中看到的那样。换句话说,数据源中的零项是堆栈中的最低项。

此代码假定您有多个部分,每个部分都有固定高度的项目,项目之间没有空格,并且是集合视图的全宽。如果您的布局更复杂,例如部分和项目之间的间距不同,或者项目高度可变,Apple 的意图是您使用 prepare() 回调来完成繁重的工作并缓存大小信息以备后用。

此代码使用 Swift 3.0.

//
//  Created by John Lyon-Smith on 1/7/17.
//  Copyright © 2017 John Lyon-Smith. All rights reserved.
//

import Foundation
import UIKit

class InvertedStackLayout: UICollectionViewLayout {
    let cellHeight: CGFloat = 100.00 // Your cell height here...

    override func prepare() {
        super.prepare()
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var layoutAttrs = [UICollectionViewLayoutAttributes]()

        if let collectionView = self.collectionView {
            for section in 0 ..< collectionView.numberOfSections {
                if let numberOfSectionItems = numberOfItemsInSection(section) {
                    for item in 0 ..< numberOfSectionItems {
                        let indexPath = IndexPath(item: item, section: section)
                        let layoutAttr = layoutAttributesForItem(at: indexPath)

                        if let layoutAttr = layoutAttr, layoutAttr.frame.intersects(rect) {
                            layoutAttrs.append(layoutAttr)
                        }
                    }
                }
            }
        }

        return layoutAttrs
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let layoutAttr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        let contentSize = self.collectionViewContentSize

        layoutAttr.frame = CGRect(
            x: 0, y: contentSize.height - CGFloat(indexPath.item + 1) * cellHeight,
            width: contentSize.width, height: cellHeight)

        return layoutAttr
    }

    func numberOfItemsInSection(_ section: Int) -> Int? {
        if let collectionView = self.collectionView,
            let numSectionItems = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: section)
        {
            return numSectionItems
        }

        return 0
    }

    override var collectionViewContentSize: CGSize {
        get {
            var height: CGFloat = 0.0
            var bounds = CGRect.zero

            if let collectionView = self.collectionView {
                for section in 0 ..< collectionView.numberOfSections {
                    if let numItems = numberOfItemsInSection(section) {
                        height += CGFloat(numItems) * cellHeight
                    }
                }

                bounds = collectionView.bounds
            }

            return CGSize(width: bounds.width, height: max(height, bounds.height))
        }
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        if let oldBounds = self.collectionView?.bounds,
            oldBounds.width != newBounds.width || oldBounds.height != newBounds.height
        {
            return true
        }

        return false
    }
}

实际上不必修改数据收集,但这会产生预期的结果。由于您控制了以下方法:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell

只是 return 通过反转请求的索引创建的单元格。索引路径是单元格在集合中的索引,不一定是源数据集中的索引。我将其用于 CoreData 集的反向显示。

let desiredIndex = dataProfile!.itemEntries!.count - indexPath[1] - 1;

Swift 4.2

我找到了一个简单的解决方案并帮助我在集合视图中首先显示最后一项:

viewDidLoad() 方法内部:

collectionView.transform = CGAffineTransform.init(rotationAngle: (-(CGFloat)(Double.pi)))

在返回单元格之前在 collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) 方法内部:

cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)

(可选)下面几行将是自动滚动和平滑滚动显示新项目所必需的。

加载新数据后添加以下行:

   if self.dataCollection.count > 0 {
      self.collectionView.scrollToItem(at: //scroll collection view to indexpath
      NSIndexPath.init(row:(self.collectionView?.numberOfItems(inSection: 0))!-1, //get last item of self collectionview (number of items -1)
      section: 0) as IndexPath //scroll to bottom of current section
      , at: UICollectionView.ScrollPosition.bottom, //right, left, top, bottom, centeredHorizontally, centeredVertically
      animated: true)
   }

只需点击 Storyboard 中的 UICollectionView, 在视图部分下的检查器菜单中,将语义更改为强制从右到左

我附上了一张图片,展示了如何在检查器菜单中执行此操作:

就这么简单:

yourCollectionView.inverted = true

PS : 同样 Texture/IGListKit..

这里有一个简单的工作解决方案!

// Change the collection view layer transform.
collectionView.transform3D = CATransform3DMakeScale(1, -1, 1)

// Change the cell layer transform.
cell.transform3D = CATransform3DMakeScale(1, -1, 1)