Populating UICollectionView in reverse order

2020-06-04 02:15发布

问题:

I would like to populate UICollectionView in reverse order so that the last item of the UICollectionView fills first and then the second last and so on. Actually I'm applying animation and items are showing up one by one. Therefore, I want the last item to show up first.

回答1:

I'm assuming you are using UICollectionViewFlawLayout, and this doesn't have logic to do that, it only works in a TOP-LEFT BOTTOM-RIGHT order. To do that you have to build your own layout, which you can do creating a new object that inherits from UICollectionViewLayout.

It seems like a lot of work but is not really that much, you have to implement 4 methods, and since your layout is just bottom-up should be easy to know the frames of each cell.

Check the apple tutorial here: https://developer.apple.com/library/prerelease/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html



回答2:

I'm surprised that Apple scares people away from writing their own UICollectionViewLayout in the documentation. It's really very straightforward. Here's an implementation that I just used in an app that will do exactly what are asking. New items appear at the bottom, and the while there is not enough content to fill up the screen the the items are bottom justified, like you see in message apps. In other words item zero in your data source is the lowest item in the stack.

This code assumes that you have multiple sections, each with items of a fixed height and no spaces between items, and the full width of the collection view. If your layout is more complicated, such as different spacing between sections and items, or variable height items, Apple's intention is that you use the prepare() callback to do the heavy lifting and cache size information for later use.

This code uses 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
    }
}


回答3:

Swift 4.2

I found a simple solution and worked for me to show last item first of a collection view:

Inside viewDidLoad() method:

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

and inside collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) method before returning the cell:

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

(optional) Below lines will be necessary to auto scroll and show new item with smooth scroll.

Add below lines after loading new data:

   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)
   }


回答4:

Just click on UICollectionView in storyboard, in inspector menu under view section change semantic to Force Right-to-Left

I have attach an image to show how to do it in the inspector menu:



回答5:

The data collection does not actually have to be modified but that will produce the expected result. Since you control the following method:

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

Simply return cells created from inverting the requested index. The index path is the cell's index in the collection, not necessarily the index in the source data set. I used this for a reversed display from a CoreData set.

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


回答6:

Don't know if this still would be useful but I guess it might be quite useful for others.

If your collection view's cells are of the same height there is actually a much less complicated solution for your problem than building a custom UICollectionViewLayout.

Firstly, just make an outlet of your collection view's top constraint and add this code to the view controller:

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

So basically you calculate the difference between collection view's height and its content only if the view's height is bigger. Then you adjust it to the constraint's constant. Pretty simple. But if you need to implement cell resizing as well, this code won't be enough. But I guess this approach may be quite useful. Hope this helps.



回答7:

It is as simple as:

yourCollectionView.inverted = true

PS : Same for Texture/IGListKit..