Create Views On Top Of Collection View? - ios

Currently, I have a calendar grid system. I am trying to implement a feature, where I can show event blocks within the calendar. So, I want to be able to implement where it is possible to add the "Untitled" blocks anywhere within the grid system by the user tapping a cell. My first thought is to add a view outside of the UICollectionView, but then when I scroll away on the calendar. The "Untitled" block would still exist, and would still be on the screen. I need to add a cell within the collection layout in order for it to stay within the flow of the collection view. To build the grid system, I had to make a custom subclass of UICollectionViewLayout, so I am not using a UICollectionViewFlowLayout. I am still a little lost how to add a cell above another cell, any ideas on the best way to implement this feature?

I ended up figuring out the answer to my own question. And, I was able to do it without having to hack the collectionView with a bunch of inner scroll views. Here's what I did. I already had my grid system in place, so I had to add an extra section to my collection view. This section's number of items was dependent upon my events data source array as such:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == Constants.numberOfSections {
//custom event section
return events.count
}
//calender grid items
return Constants.numberOfColumns
}
But, my custom layout for the collection view needed to be updated about the cellAttributes. If I did not tell it that a new cell was added, then it would either crash because it couldn't find a corresponding grid member or add the cell to the grid, like it was another part of the grid. So, I had to update the cellAttributes in my custom layout class, and then manually calculate where the cell should be placed within the layout. So, basically every time I add an event cell, the layout has to manually calculate where that cell should be within the grid. This is the function where I manually calculate the events coordinates in my custom layout subclass (the relevant parts is in the custom events comments):
fileprivate func setCellAttributes(item: Int, section: Int) {
// Build the UICollectionVieLayoutAttributes for the cell.
let cellIndex = IndexPath(item: item, section: section)
var cellWidth: Double = CELL_WIDTH
var cellHeight: Double = CELL_HEIGHT
var xPos: Double = 0
var yPos = Double(section) * CELL_HEIGHT
if section == collectionView!.numberOfSections - 1 {
//custom event items
let rect = getCustomEventRect(item: item)
xPos = Double(rect.x)
yPos = Double(rect.y)
cellHeight = Double(rect.height)
cellWidth = Double(rect.width)
} else if item == 0 {
//the y axis cells
cellWidth = yAxisCellWidth
} else {
//all other cells
xPos = calculateXPos(item: item)
}
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: cellWidth, height: cellHeight)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
//top left corner cell
cellAttributes.zIndex = 5
} else if section == 0 {
//y axis cells
cellAttributes.zIndex = 4
} else if section == collectionView!.numberOfSections - 1 {
//custom event cells
cellAttributes.zIndex = 2
} else if item == 0 {
//top x axis cells
cellAttributes.zIndex = 3
} else {
//all background schedule cells
cellAttributes.zIndex = 1
}
// Save the attributes.
cellAttrsDictionary[cellIndex] = cellAttributes
}
Also, every time I updated the events in my viewController, I had to update the events in my layout. So, I implemented this in my viewController:
var events: [CustomEvent] = [] {
didSet {
if let layout = theCollectionView.collectionViewLayout as? ScheduleCollectionViewLayout {
layout.events = events
}
}
}
And, when a user taps to add a new event, I make sure to update the layout accordingly:
func removeEventCell(at indexPath: IndexPath) {
let eventSection: Int = collectionView!.numberOfSections - 1
let totalEventItems: Int = collectionView!.numberOfItems(inSection: eventSection)
//decrementing all indexPaths above the deleted event cell, so the attribute dictionary will be up to date, when reloadSections is run by the collectionView.
for item in 0..<totalEventItems where item > indexPath.item {
let targetIndexPath = IndexPath(item: item - 1, section: eventSection)
let cellAttr = cellAttrsDictionary[IndexPath(item: item, section: eventSection)]
cellAttr?.indexPath = targetIndexPath
cellAttrsDictionary[targetIndexPath] = cellAttr
}
let lastIndexPath = IndexPath(item: totalEventItems - 1, section: eventSection)
cellAttrsDictionary.removeValue(forKey: lastIndexPath)
}
fileprivate func addEventCellAttributes(numOfEventsToAdd: Int) {
for num in 1...numOfEventsToAdd {
setCellAttributes(item: events.count - num, section: collectionView!.numberOfSections - 1)
}
}
This is a very manual process of calculating where to put the cells, but it keeps the collectionView working smoothly and allows the functionality of having cells above cells. The summary of my answer is that I added a new section for my custom events, and I had to manually calculate where the positions of these cells should be, rather than just having them flow with the grid system. My interactive calendar now works perfectly. If you're having trouble trying to even create a grid system, I used this tutorial:
https://www.credera.com/blog/mobile-applications-and-web/building-a-multi-directional-uicollectionview-in-swift/

Related

How to stop collection triggering code when scrolling back in to view swift

I'm pretty new so apologies if my title doesn't phrase things correctly. I've been hacking away and haven't been able to find an answer to this problem.
I have a horizontally scrolling collection. I'm trying to visually show poll results programatically adding CGRect to the relevant item in the collection based on voting data held in a Firestore array.
This is working, but the problem is when you scroll away (so the item in the collection is off screen) and then back, the code to draw the CGRects gets triggered again and more graphics get added to the view. Is there a way to delete these CGRects when the user scrolls an item collection off screen so when the user scrolls the item back into view, code is triggered again it doesn't create duplicates?
Here are a couple of screenshots showing first and second load
Here is my code (cell b is where the CGrect gets triggered)
//COLLECTION VIEW CODE
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.sentCollectionView {
let cellA = collectionView.dequeueReusableCell(withReuseIdentifier: "sCell", for: indexPath) as! SentCollectionViewCell
cellA.sentQuestionLabel.text = sentMessages[indexPath.row].shoutText
// Set up cell
return cellA
}
else {
let cellB = receivedCollectionView.dequeueReusableCell(withReuseIdentifier: "pCell", for: indexPath) as! ReceivedCollectionViewCell
receivedMessages[indexPath.row].pollTotal = receivedMessages[indexPath.row].pollResults.reduce(0, +)
print("Sum of Array is : ", receivedMessages[indexPath.row].pollTotal!)
cellB.receivedShoutLabel.text = receivedMessages[indexPath.row].shoutText
print(receivedMessages[indexPath.row].pollResults.count)
if receivedMessages[indexPath.row].pollResults != [] {
for i in 0...receivedMessages[indexPath.row].pollResults.count - 1 {
cellB.resultsView.addSubview(sq(pollSum: receivedMessages[indexPath.row].pollTotal!, pollResult: receivedMessages[indexPath.row].pollResults[i]))
}
}
return cellB
}
}
//THIS DRAWS THE CGRECT
func sq(pollSum: Int, pollResult: Int) -> UIView {
// divide the width by total responses
let screenDivisions = Int(view.frame.size.width) / pollSum
// the rectangle top left point x axis position.
let xPos = 0
// the rectangle width.
let rectWidth = pollResult * screenDivisions
// the rectangle height.
let rectHeight = 10
// Create a CGRect object which is used to render a rectangle.
let rectFrame: CGRect = CGRect(x:CGFloat(xPos), y:CGFloat(yPos), width:CGFloat(rectWidth), height:CGFloat(rectHeight))
// Create a UIView object which use above CGRect object.
let greenView = UIView(frame: rectFrame)
// Set UIView background color.
greenView.backgroundColor = UIColor.green
//increment y position
yPos = yPos + 25
return greenView
}
Cells of collection are dequeued dequeueReusableCell , you need to override prepareForReuse
Or set a tag
greenView.tag = 333
And inside cellForItemAt do this
cellB.resultsView.subviews.forEach {
if $0.tag == 333 {
$0.removeFromSuperview()
}
}
if receivedMessages[indexPath.row].pollResults != [] {
for i in 0...receivedMessages[indexPath.row].pollResults.count - 1 {
cellB.resultsView.addSubview(sq(pollSum: receivedMessages[indexPath.row].pollTotal!, pollResult: receivedMessages[indexPath.row].pollResults[i]))
}
}

How to make paging animation for a collectionView cell scrolling to center?

I am using swift 3 to write an app which has a UIViewController looks like the following picture. It contains a horizontal-scrollable collectionView (the blue block) on the top, and its functionality is to switch between different labels(first, prev, current, next, last...etc, the total amount of labels are not fixed). What I need to achieve is that on scrolling (or panning) the collection view, the 'NEXT' or 'PREV' should move (and anchor automatically) to the center with velocity similar to paging animation. How could I achieve this? Currently I use the Paging Enabled attribute of the scrollView, however this only work when there is one label in the window.
Similar features may look like the middle "albums" section of iTunes app, which the collectionView cell would scroll to some pre-defined point automatically with some animation once it detects any swipe/pan gesture.
according to your comment if you want to do the Tap to select thing there are 2 things you need to do
1- Disable Collection View Scrolling in viewDidLoad
collectionView.isScrollingEnabled = false
2- Display Selected Cell in Center , add this in your didSelectItemAtIndexPath method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collection.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizantally)
let cell = collectionView.cellForItem(at : indexPath) as! YourCollectionViewCellClass
cell.titleLable.font = UIFont.boldSystemFont(ofSize : 20)
}
Thanks to Raheel's answer here, I finally find a way for a desirable scroll effect. Some key functions are listed as follow in swift:
First, disable Paging Enabled attribute of the scrollView
Define setIdentityLayout() and setNonIdentityLayout() which defines the layouts of both selected / non-selected cell (for example, MyCollectionViewCell in this case)
Define some related properties such as:
lazy var COLLECTION_VIEW_WIDTH: CGFloat = {
return CGFloat(self.collectionView.frame.size.width / 2)
}()
fileprivate let TRANSFORM_CELL_VALUE = CGAffineTransform(scaleX: 1, y: 1) // Or define other transform effects here
fileprivate let ANIMATION_SPEED = 0.2
implement the following key methods for UIScrollViewDelegate:
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
// Do tableView data refresh according to the corresponding pages here
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Scroll to corresponding position
let pageWidth: Float = Float(COLLECTION_VIEW_WIDTH) // width + space
let currentOffset: Float = Float(scrollView.contentOffset.x)
let targetOffset: Float = Float(targetContentOffset.pointee.x)
var newTargetOffset: Float = 0
if targetOffset > currentOffset {
newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth
}
else {
newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth
}
if newTargetOffset < 0 {
newTargetOffset = 0
}
else if (newTargetOffset > Float(scrollView.contentSize.width)){
newTargetOffset = Float(Float(scrollView.contentSize.width))
}
targetContentOffset.pointee.x = CGFloat(currentOffset)
scrollView.setContentOffset(CGPoint(x: CGFloat(newTargetOffset), y: scrollView.contentOffset.y), animated: true)
// Set transforms
let identityIndex: Int = Int(newTargetOffset / pageWidth)
var cell = delegate!.roomCollections.cellForItem(at: IndexPath.init(row: identityIndex, section: 0)) as? MyCollectionViewCell
UIView.animate(withDuration: ANIMATION_SPEED, animations: {
cell?.transform = CGAffineTransform.identity
cell?.setIdentityLayout()
})
// right cell
cell = delegate!.roomCollections.cellForItem(at: IndexPath.init(row: identityIndex + 1, section: 0)) as? MyCollectionViewCell
UIView.animate(withDuration: ANIMATION_SPEED, animations: {
cell?.transform = self.TRANSFORM_CELL_VALUE
cell?.setNonIdentityLayout()
})
// left cell, which is not necessary at index 0
if (identityIndex != 0) {
cell = delegate!.roomCollections.cellForItem(at: IndexPath.init(row: identityIndex - 1, section: 0)) as? MyCollectionViewCell
UIView.animate(withDuration: ANIMATION_SPEED, animations: {
cell?.transform = self.TRANSFORM_CELL_VALUE
cell?.setNonIdentityLayout()
})
}
}
Finally, define initial layout in func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath):
if (indexPath.row == 0 && /* other first item selected condition */) {
cell.setIdentityLayout()
} else {
cell.transform = TRANSFORM_CELL_VALUE
cell.setNonIdentityLayout()
}
To add touch scroll effect, simply add target to each collection views, and do similar calculation which changes the contentOffSet of the scrollView, which is omitted here because this feature is simpler and not the primary question.

UICollectionView / FlowLayout

I'm facing a challenge when using UICollectionView and a related Layout. I'm trying to create a vertical UICollectionView with two columns, where as the cells in the left and right columns are positioned as displayed in the picture:
I do manage without problems to create the two columns but struggle finding the correct settings in my Layout in order to have the right column be offset by half the height of one cell. Note, all cells have the same calculated dimension based on the width of the screen (minus the spacing)...
Every cell in my data array has got a position index, I can thus easily find out whether a cell is positioned right or left based base on it (odd / even)
Any help is gladly appreciated
Here's how I would implement a subclass of UICollectionViewFlowLayout to achieve what you want.
class OffsetFlowLayout: UICollectionViewFlowLayout {
var verticalOffset: CGFloat = 0
override func prepare() {
super.prepare()
verticalOffset = 100 // Calculate offset here
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.layoutAttributesForItem(at: indexPath) else { return nil }
guard remainder(Double(indexPath.row), 2) != 0 else { return attributes }
// For each item in the right column, offset the y value of it's origin
attributes.frame.origin.y += verticalOffset
return attributes
}
}
As you can see, on our implementation of layoutAttributesForItem the first thing we do is to call super and store the value it returns. Then we check the index and if we are on the left column, we return what super gave us, which would be the "default" value.
If we're on the right column, we modify the attributes object to move the frame of the cell by the offset we want, and then return that one.
NOTE: I haven't tested this. There's a chance the UICollectionViewFlowLayout uses the previous cells to layout the next cells, in which case you would only need to modify the first element in the right column, just change guard remainder(Double(indexPath.row), 2) != 0 else { return attributes } to guard indexPath.row == 1 else { return attributes }

Why aren't the cells in my UICollectionView updating their size when I update the flow layout?

I have a grid of cells in a UICollectionViewController:
Pretty standard 3 column grid (based on approximate cell size, wider layouts have more cells)
The overall idea of the app is that a user taps a cell which counts down the specified time, and once the timer is complete it will unlock the next time. The first time is always unlocked:
After the first 3 buttons are tapped and have counted down
Tapping the same cell twice in succession removes all cells except the first and the one which was tapped twice, to allow the user to loop around.
Doing this should also update the collectionViewLayout to be only 2 columns of equal width which fill the screen, but at the moment the first and second cells are not resizing:
[After a button is tapped twice in succession](http:// i.imgur.com/cLvaApG.png)
The layout is definitely updated / invalidated because rotating the device updates the cells as expected:
[Rotated to landscape mode](http:// i.imgur.com/rngwzre.png)
[And rotated back to portrait](http:// i.imgur.com/ajwPr1J.png)
I'm sizing the cells as follows:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if repeating {
let target = collectionView.frame.width
let targetFraction = target / 2
return CGSize(width: targetFraction, height: targetFraction)
}
let target = CGFloat(125)
let frameRef = collectionView.frame.width
let frameWidthRemainder = frameRef % target
let frameWidthRounded = frameRef - frameWidthRemainder
let columnCount = frameWidthRounded / target
let columnWidth = frameRef / columnCount
return CGSize(width: columnWidth, height: columnWidth)
}
Repeating is a bool which is set / reset by tapping on the cells as part of an action:
#IBAction func circleProgressButtonTapped(sender: CircleProgressButton) {
...
if previouslySelectedButton == sender && !repeating {
repeating = true
collectionViewLayout.invalidateLayout()
buttons = [buttons[0], buttons[row]]
collectionView?.reloadData()
for button in buttons {
button.circleProgressButton.setNeedsDisplay()
}
}
...
}
When the device rotates, I'm resetting the layout with:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
for button in buttons {
button.circleProgressButton.setNeedsDisplay()
}
collectionViewLayout.invalidateLayout()
}
Buttons is an array of the cells, which is populated / reset with:
func refreshView() {
if let min = defaults.valueForKey(userDefaultsKeys.min.rawValue) as? Int {
self.min = min
}
if let max = defaults.valueForKey(userDefaultsKeys.max.rawValue) as? Int {
self.max = max
}
buttons = []
collectionView?.reloadData()
for i in 0..<buttonCount {
guard let collectionView = collectionView else { break }
let indexPath = NSIndexPath(forRow: i, inSection: 0)
let multiplier = i + 1
let time = min * multiplier
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CircleProgressButtonCollectionViewCell
cell.circleProgressButton.delegate = self
cell.circleProgressButton.value = time
cell.circleProgressButton.resetText()
cell.circleProgressButton.indexPath = indexPath
cell.circleProgressButton.progress = 0
cell.circleProgressButton.activeProgressColour = theme
cell.circleProgressButton.tintColor = theme
cell.circleProgressButton.setNeedsDisplay()
if i > 0 {
cell.circleProgressButton.enabled = false
}
buttons.append(cell)
}
}
I know this is probably not the best way to be making the cells, but any other way caused problems with the cell re-use. I.e. the progress in a cell would also be re-used.
I'm not even sure if the issue is collectionViewLayout.invalidateLayout() is not invalidating properly or what. It could just be the collectionView needs to be updated in some other way. I don't even know if it's the collectionView or the cell I should be focusing on. It's just weird that the first two cells are the only ones not updating, especially as I'm not doing anything different to them.
I would list everything I've tried but I'd be here a while. Among other things, I've tried:
if previouslySelectedButton == sender && !repeating {
...
collectionViewLayout.prepareLayout()
if let collectionView = collectionView {
collectionView.setNeedsDisplay()
collectionView.setNeedsLayout()
}
if let collectionViewLayoutCollectionView = collectionViewLayout.collectionView {
collectionViewLayoutCollectionView.setNeedsDisplay()
collectionViewLayoutCollectionView.setNeedsLayout()
}
collectionView?.setNeedsDisplay()
collectionViewLayout.collectionView?.setNeedsDisplay()
collectionView?.setNeedsLayout()
collectionViewLayout.collectionView?.setNeedsLayout()
collectionView?.reloadData()
buttons = [buttons[0], buttons[row]]
for button in buttons {
button.setNeedsLayout()
button.setNeedsDisplay()
}
I've tried setting the buttons array to empty, reloading, then populating it (generating entirely new cells) and reloading it again but even that doesn't work.
If I have to radically alter my entire approach, so be it.

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.
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)
}
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
}
}
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:
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
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;
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.
A simple working solution is here!
// Change the collection view layer transform.
collectionView.transform3D = CATransform3DMakeScale(1, -1, 1)
// Change the cell layer transform.
cell.transform3D = CATransform3DMakeScale(1, -1, 1)
It is as simple as:
yourCollectionView.inverted = true
PS : Same for Texture/IGListKit..

Resources