Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Populating UICollectionView in reverse order

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.

like image 426
Mughees Musaddiq Avatar asked Jul 08 '15 06:07

Mughees Musaddiq


2 Answers

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
    }
}
like image 146
jlyonsmith Avatar answered Nov 06 '22 04:11

jlyonsmith


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;
like image 39
Daniel Avatar answered Nov 06 '22 04:11

Daniel