I'm using the Collection view with vertical scroll direction for one my application. Here, I want to make my Collection view to display upside down without using any CGAffine Transform. Is it possible with the help of Flow Layout?
I like to implement the chat by using collection view
Thanks in advance
class ChatCollectionViewFlowLayout: UICollectionViewFlowLayout {
private var topMostVisibleItem = Int.max
private var bottomMostVisibleItem = -Int.max
private var offset: CGFloat = 0.0
private var visibleAttributes: [UICollectionViewLayoutAttributes]?
private var isInsertingItemsToTop = false
private var isInsertingItemsToBottom = false
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Reset each time all values to recalculate them
// ════════════════════════════════════════════════════════════
// Get layout attributes of all items
visibleAttributes = super.layoutAttributesForElements(in: rect)
// Erase offset
offset = 0.0
// Reset inserting flags
isInsertingItemsToTop = false
isInsertingItemsToBottom = false
return visibleAttributes
}
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
// Check where new items get inserted
// ════════════════════════════════════════════════════════════
// Get collection view and layout attributes as non-optional object
guard let collectionView = self.collectionView else { return }
guard let visibleAttributes = self.visibleAttributes else { return }
// Find top and bottom most visible item
// ────────────────────────────────────────────────────────────
bottomMostVisibleItem = -Int.max
topMostVisibleItem = Int.max
let container = CGRect(x: collectionView.contentOffset.x,
y: collectionView.contentOffset.y,
width: collectionView.frame.size.width,
height: (collectionView.frame.size.height - (collectionView.contentInset.top + collectionView.contentInset.bottom)))
for attributes in visibleAttributes {
// Check if cell frame is inside container frame
if attributes.frame.intersects(container) {
let item = attributes.indexPath.item
if item < topMostVisibleItem { topMostVisibleItem = item }
if item > bottomMostVisibleItem { bottomMostVisibleItem = item }
}
}
// Call super after first calculations
super.prepare(forCollectionViewUpdates: updateItems)
// Calculate offset of inserting items
// ────────────────────────────────────────────────────────────
var willInsertItemsToTop = false
var willInsertItemsToBottom = false
// Iterate over all new items and add their height if they go inserted
for updateItem in updateItems {
switch updateItem.updateAction {
case .insert:
if topMostVisibleItem + updateItems.count > updateItem.indexPathAfterUpdate!.item {
if let newAttributes = self.layoutAttributesForItem(at: updateItem.indexPathAfterUpdate!) {
offset += (newAttributes.size.height + self.minimumLineSpacing)
willInsertItemsToTop = true
}
} else if bottomMostVisibleItem <= updateItem.indexPathAfterUpdate!.item {
if let newAttributes = self.layoutAttributesForItem(at: updateItem.indexPathAfterUpdate!) {
offset += (newAttributes.size.height + self.minimumLineSpacing)
willInsertItemsToBottom = true
}
}
case.delete:
// TODO: Handle removal of items
break
default:
break
}
}
// Pass on information if items need more than one screen
// ────────────────────────────────────────────────────────────
// Just continue if one flag is set
if willInsertItemsToTop || willInsertItemsToBottom {
// Get heights without top and bottom
let collectionViewContentHeight = collectionView.contentSize.height
let collectionViewFrameHeight = collectionView.frame.size.height - (collectionView.contentInset.top + collectionView.contentInset.bottom)
// Continue only if the new content is higher then the frame
// If it is not the case the collection view can display all cells on one screen
if collectionViewContentHeight + offset > collectionViewFrameHeight {
if willInsertItemsToTop {
CATransaction.begin()
CATransaction.setDisableActions(true)
isInsertingItemsToTop = true
} else if willInsertItemsToBottom {
isInsertingItemsToBottom = true
}
}
}
}
override func finalizeCollectionViewUpdates() {
// Set final content offset with animation or not
// ════════════════════════════════════════════════════════════
// Get collection view as non-optional object
guard let collectionView = self.collectionView else { return }
if isInsertingItemsToTop {
// Calculate new content offset
let newContentOffset = CGPoint(x: collectionView.contentOffset.x,
y: collectionView.contentOffset.y + offset)
// Set new content offset without animation
collectionView.contentOffset = newContentOffset
// Commit/end transaction
CATransaction.commit()
} else if isInsertingItemsToBottom {
// Calculate new content offset
// Always scroll to bottom
let newContentOffset = CGPoint(x: collectionView.contentOffset.x,
y: collectionView.contentSize.height + offset - collectionView.frame.size.height + collectionView.contentInset.bottom)
// Set new content offset with animation
collectionView.setContentOffset(newContentOffset, animated: true)
}
}
}
also please check, it may set collection view As Chat behavior like upside down
https://gist.github.com/jochenschoellig/04ffb26d38ae305fa81aeb711d043068
Related
I am trying to create tag list view. Unfortunately I don't know How to work with Custom flow layout. But I found to create pretty tag list. But encountered a problem with multi-line label. If we used the text more than the collection view width, then the attribute.frame.height should be multiplied like 40*numberOfLines. Please help to solve this problem.
Custom Flow Layout:
import UIKit
class CollectionViewRow {
var attributes = [UICollectionViewLayoutAttributes]()
var spacing: CGFloat = 0
init(spacing: CGFloat) {
self.spacing = spacing
}
func add(attribute: UICollectionViewLayoutAttributes) {
attributes.append(attribute)
}
var rowWidth: CGFloat {
return attributes.reduce(0, { result, attribute -> CGFloat in
return result + attribute.frame.width
}) + CGFloat(attributes.count - 1) * spacing
}
func layout(collectionViewWidth: CGFloat) {
var offset = spacing
for attribute in attributes {
attribute.frame.origin.x = offset
offset += attribute.frame.width + spacing
}
}
}
class UICollectionViewCenterLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else {
return nil
}
var rows = [CollectionViewRow]()
var currentRowY: CGFloat = -1
for attribute in attributes {
if currentRowY != attribute.frame.midY {
currentRowY = attribute.frame.midY
rows.append(CollectionViewRow(spacing: 10))
}
rows.last?.add(attribute: attribute)
}
rows.forEach { $0.layout(collectionViewWidth: collectionView?.frame.width ?? 0) }
return rows.flatMap { $0.attributes }
}
}
Usage:
let layout = UICollectionViewCenterLayout()
layout.estimatedItemSize = CGSize(width: 140, height: 40)
collectionView.collectionViewLayout = layout
collectionView.reloadData()
I tried to change height but It not working expected:
func layout(collectionViewWidth: CGFloat) {
var offset = spacing
for attribute in attributes {
attribute.frame.origin.x = offset
let attributeTotalWidth = attribute.frame.width + spacing
if attributeTotalWidth > collectionViewWidth{
let multiplier: CGFloat = attributeTotalWidth/collectionViewWidth
let intVal = CGFloat(Int(multiplier))
let fullNumber = multiplier-intVal > 0 ? intVal+1 :
attribute.frame.size.height = fullNumber * 40
}
offset += attributeTotalWidth
}
}
Can you please help me to find out the solution to make variable item height based on the label text content? Thanking you in advance!
#iDeveloper I tried to fix your code.
Here are the things you can do.
Simplify your array.
let titles = ["Apple","Google","Computer", "Terminal", "Gross", "Form lands", "Water lands", "river", "mounts", "trees", "places", "parks", "towns", "cities","Lorem Ipsum is simply dummy text of the printing and typesettingindustry.","Hello","A","B","CAD","John","Nick","Lucas","Amy","Lawrance","Apple","Google","Computer", "Browser","Overflow","Hi","Hello","A","B","CAD","John","Nick","Lucas","Amy","Lawrance", "Lorem Ipsum is simply dummy text of the printing and typesetting industry.",""]
You have to add a width constraint to your title label like this.
Set content hugging priority for you label
End result
One could make the below code prettier by moving more code into Row, but it fixes the issue of clipped labels (as seen in the result image of the other answer). The code is also a lot simpler and still works with labels that contain multiple lines (label.numberOfLines = 0)
class CustomCollectionViewLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else {
return nil
}
let collectionViewWidth = collectionView?.frame.width ?? 0
var rows = [Row]()
let spacing: CGFloat = 10
var offsetY: CGFloat = 0
var offsetX: CGFloat = spacing
rows.append(Row())
for attribute in attributes {
let neededWidth = CGFloat(attribute.frame.width) + spacing
let neededMaxX = offsetX + neededWidth
if neededMaxX <= collectionViewWidth {
attribute.frame.origin.x = offsetX
attribute.frame.origin.y = offsetY
offsetX = neededMaxX
rows.last?.add(attribute: attribute)
} else {
attribute.frame.origin.x = spacing
offsetX = attribute.frame.origin.x + neededWidth
offsetY += attribute.frame.height + spacing
attribute.frame.origin.y = offsetY
rows.append(Row())
rows.last?.add(attribute: attribute)
}
}
return rows.flatMap { $0.attributes }
}
}
class Row {
var attributes = [UICollectionViewLayoutAttributes]()
init() {}
func add(attribute: UICollectionViewLayoutAttributes) {
attributes.append(attribute)
}
}
I came across this demo and really want to learn how to do this:
So far, I've created a subclass to UICollectionViewFlowLayout that fades out the start and end of the cells. Taken from https://gist.github.com/vinhnx/bb1354b247ebfe3790563173ac72baa9
This is the part that fades out the start and end cells
for attrs in attributes {
if attrs.frame.intersects(rect) {
let distance = visibleRect.midY - attrs.center.y
let normalizedDistance = abs(distance) / (visibleRect.height * fadeFactor)
let fade = 1 - normalizedDistance
attrs.alpha = fade
}
}
How do I make the cells that is at the top stand still and not continue to slide up?
Full code of the function handling the fade:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributesSuper: [UICollectionViewLayoutAttributes] = super.layoutAttributesForElements(in: rect) as [UICollectionViewLayoutAttributes]!
if let attributes = NSArray(array: attributesSuper, copyItems: true) as? [UICollectionViewLayoutAttributes]{
var visibleRect = CGRect()
visibleRect.origin = collectionView!.contentOffset
visibleRect.size = collectionView!.bounds.size
for attrs in attributes {
if attrs.frame.intersects(rect) {
let distance = visibleRect.midY - attrs.center.y
let normalizedDistance = abs(distance) / (visibleRect.height * fadeFactor)
let fade = 1 - normalizedDistance
attrs.alpha = fade
print(fade)
}
}
return attributes
}else{
return nil
}
}
The look of my app right now:
I downloaded this Pintrest-esc Custom Layout for my CollectionView from : https://www.raywenderlich.com/164608/uicollectionview-custom-layout-tutorial-pinterest-2 (modified it a little bit to do with cache code)
So the layout works fine, i got it set up so when you scroll to the bottom, i make another API call, create and populate additional cells.
Problem: If a User scrolls down and loads some more cells (lets say 20 for example) and then uses the "Search Feature" i have implemented at the top, it will then filter some of the data via price from MinRange - MaxRange leaving you with (lets say 5 results), thus deleting some cells and repopulating those. BUT - All that space and scrollable space that existed for the first 20 cells still exists as blank white space.... How can i remove this space ? and why can it resize when adding more cells, but not when removing them ?
Code of my API Call:
DispatchQueue.global(qos: .background).async {
WebserviceController.GetSearchPage(parameters: params) { (success, data) in
if success
{
if let products = data!["MESSAGE"] as? [[String : AnyObject]]
{
self.productList = products
}
}
DispatchQueue.main.async(execute: { () -> Void in
self.pintrestCollectionView.reloadData()
self.pintrestCollectionView.collectionViewLayout.invalidateLayout()
self.pintrestCollectionView.layoutSubviews()
})
}
}
Code Of Custom Layout Class & protocol:
protocol PinterestLayoutDelegate: class {
func collectionView(_ collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat
}
class PintrestLayout: UICollectionViewFlowLayout {
// 1
weak var delegate : PinterestLayoutDelegate!
// 2
fileprivate var numberOfColumns = 2
fileprivate var cellPadding: CGFloat = 10
// 3
fileprivate var cache = [UICollectionViewLayoutAttributes]()
// 4
fileprivate var contentHeight: CGFloat = 0
fileprivate var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
// 5
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
// 1
//You only calculate the layout attributes if cache is empty and the collection view exists.
/*guard cache.isEmpty == true, let collectionView = collectionView else {
return
}*/
cache.removeAll()
guard cache.isEmpty == true || cache.isEmpty == false, let collectionView = collectionView else {
return
}
// 2
//This declares and fills the xOffset array with the x-coordinate for every column based on the column widths. The yOffset array tracks the y-position for every column.
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
// 3
//This loops through all the items in the first section, as this particular layout has only one section.
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
if collectionView.numberOfItems(inSection: 0) < 3
{
collectionView.isScrollEnabled = false
} else {
collectionView.isScrollEnabled = true
}
let indexPath = IndexPath(item: item, section: 0)
// 4
//This is where you perform the frame calculation. width is the previously calculated cellWidth, with the padding between cells removed.
let photoHeight = delegate.collectionView(collectionView, heightForPhotoAtIndexPath: indexPath)
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: 7, dy: 4)
collectionView.contentInset = UIEdgeInsets.init(top: 15, left: 12, bottom: 0, right: 12)
// 5
//This creates an instance of UICollectionViewLayoutAttribute, sets its frame using insetFrame and appends the attributes to cache.
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// 6
//This expands contentHeight to account for the frame of the newly calculated item.
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
{
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
// Loop through the cache and look for items in the rect
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
FYI: In MainStoryboard, i disabled "Adjust Scroll View Insets" as many of other threads and forums have suggested...
I'm guessing you've already found the answer, but I'm posting here for the next person since I ran into this issue.
The trick here is contentHeight actually never gets smaller.
contentHeight = max(contentHeight, frame.maxY)
This is a nondecreasing function.
To fix it, just set contentHeight to 0 if prepare is called:
guard cache.isEmpty == true || cache.isEmpty == false, let
collectionView = collectionView else {
return
}
contentHeight = 0
Hello I have working uicollectionview custom layout and i have issuesfor bottom padding and scrolling sizes. Under below picture you can see;
Also top side first cell always stays on top when scrolling ( Left side cell must be always stay right now no problem there , but top side cell must be scroll when scrolling )
I try to set in viewDidLoad under below codes but dont work
self.automaticallyAdjustsScrollViewInsets = false
self.collectionView.contentInset = UIEdgeInsetsMake(20, 20, 120, 100);
My custom collectionview layout file
import UIKit
public var CELL_HEIGHT = CGFloat(22)
protocol CustomCollectionViewDelegateLayout: NSObjectProtocol {
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, widthForItemAtIndexPath indexPath: NSIndexPath) -> CGFloat
}
class CustomCollectionViewLayout: UICollectionViewLayout {
// Used for calculating each cells CGRect on screen.
// CGRect will define the Origin and Size of the cell.
let STATUS_BAR = UIApplication.sharedApplication().statusBarFrame.height
// Dictionary to hold the UICollectionViewLayoutAttributes for
// each cell. The layout attribtues will define the cell's size
// and position (x, y, and z index). I have found this process
// to be one of the heavier parts of the layout. I recommend
// holding onto this data after it has been calculated in either
// a dictionary or data store of some kind for a smooth performance.
var cellAttrsDictionary = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>()
// Defines the size of the area the user can move around in
// within the collection view.
var contentSize = CGSize.zero
// Used to determine if a data source update has occured.
// Note: The data source would be responsible for updating
// this value if an update was performed.
var dataSourceDidUpdate = true
weak var delegate: CustomCollectionViewDelegateLayout?
override func collectionViewContentSize() -> CGSize {
return self.contentSize
}
override func prepareLayout() {
// Only update header cells.
if !dataSourceDidUpdate {
// Determine current content offsets.
let xOffset = collectionView!.contentOffset.x
let yOffset = collectionView!.contentOffset.y
if collectionView?.numberOfSections() > 0 {
for section in 0...collectionView!.numberOfSections()-1 {
// Confirm the section has items.
if collectionView?.numberOfItemsInSection(section) > 0 {
// Update all items in the first row.
if section == 0 {
for item in 0...collectionView!.numberOfItemsInSection(section)-1 {
// Build indexPath to get attributes from dictionary.
let indexPath = NSIndexPath(forItem: item, inSection: section)
// Update y-position to follow user.
if let attrs = cellAttrsDictionary[indexPath] {
var frame = attrs.frame
// Also update x-position for corner cell.
if item == 0 {
frame.origin.x = xOffset
}
frame.origin.y = yOffset
attrs.frame = frame
}
}
// For all other sections, we only need to update
// the x-position for the fist item.
} else {
// Build indexPath to get attributes from dictionary.
let indexPath = NSIndexPath(forItem: 0, inSection: section)
// Update y-position to follow user.
if let attrs = cellAttrsDictionary[indexPath] {
var frame = attrs.frame
frame.origin.x = xOffset
attrs.frame = frame
}
}
}
}
}
// Do not run attribute generation code
// unless data source has been updated.
return
}
// Acknowledge data source change, and disable for next time.
dataSourceDidUpdate = false
var maxWidthInASection = CGFloat(0)
// Cycle through each section of the data source.
if collectionView?.numberOfSections() > 0 {
for section in 0...collectionView!.numberOfSections()-1 {
// Cycle through each item in the section.
if collectionView?.numberOfItemsInSection(section) > 0 {
var prevCellAttributes: UICollectionViewLayoutAttributes?
for item in 0...collectionView!.numberOfItemsInSection(section)-1 {
let cellIndex = NSIndexPath(forItem: item, inSection: section)
guard let width = delegate?.collectionView(collectionView!, layout: self, widthForItemAtIndexPath: cellIndex) else {
print("Please comform to CustomCollectionViewDelegateLayout protocol")
return
}
// Build the UICollectionVieLayoutAttributes for the cell.
var xPos = CGFloat(0)
let yPos = CGFloat(section) * CELL_HEIGHT
if let prevCellAttributes = prevCellAttributes {
xPos = CGRectGetMaxX(prevCellAttributes.frame)
}
let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: width, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
// Save the attributes.
cellAttrsDictionary[cellIndex] = cellAttributes
prevCellAttributes = cellAttributes
let maxX = CGRectGetMaxX(cellAttributes.frame)
if maxWidthInASection < maxX {
maxWidthInASection = maxX
}
}
}
}
}
// Update content size.
let contentWidth = maxWidthInASection
let contentHeight = Double(collectionView!.numberOfSections())
self.contentSize = CGSize(width: Double(contentWidth), height: contentHeight)
self.contentSize.height = CGFloat(Double(collectionView!.numberOfSections()))
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Create an array to hold all elements found in our current view.
var attributesInRect = [UICollectionViewLayoutAttributes]()
// Check each element to see if it should be returned.
for cellAttributes in cellAttrsDictionary.values.elements {
if CGRectIntersectsRect(rect, cellAttributes.frame) {
attributesInRect.append(cellAttributes)
}
}
// Return list of elements.
return attributesInRect
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary[indexPath]!
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
}
I try change collecionview properties in storyboard but doesnt works also i attached current collectionview object properties picture under below
Also i selected zoom 4 but zoom feature doesnt work ? why ? it must be work also zoom feature.
I know this is too late to answer, but this could be useful to other users.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 0)
}
This what worked for me:
messagesCollectionView.contentInset.top = 100
messagesCollectionView.contentInset.bottom = 80
This can be also done on IB under section insets...
I've subclassed a UICollectionViewFlowLayout to achieve a small scaling effect during horizontal scroll. Therefore I had to subclass a UICollectionViewCell as well as to change the layer.anchorPoint of the cell (my scaling is from the bottom left of the cell rather than from the default center). Now all fine and well except the fact that when I am scrolling horizontally , my cell is reused too soon (I still can see the half cell when it suddenly disappear ).
I have the feeling that collection view still bases its calculations for reusing the cell on the anchor point positioned in the center of the cell...
However , this is my collection view . You can see how the item getting bigger as it reaches the left side of the collection view. This is the scaling I wanted.
Now here I am scrolling to the left. You can see how the right item became bigger and the left is getting out of the screen.
And here you see that the left item didn't get off the screen but already dissapeared. Only the right item remeained visible :/
So what I want is , to make the left item disappear only when it's right boundaries reaching the very left of the screen.Simply saying , to dissapear only when I don't see it anymore.
And here is my code:
class SongsCollectionViewCell : UICollectionViewCell {
#IBOutlet weak var imgAlbumCover: UIImageView!
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
//we must change the anchor point for propper cells positioning and scaling
self.layer.anchorPoint.x = 0
self.layer.anchorPoint.y = 1
}
}
Here is the layout itself :
class SongsCollectionViewLayout : UICollectionViewFlowLayout {
override func prepareLayout() {
collectionView?.decelerationRate = UIScrollViewDecelerationRateFast
self.scrollDirection = UICollectionViewScrollDirection.Horizontal;
//size of the viewport
let size:CGSize = self.collectionView!.frame.size;
let itemWidth:CGFloat = size.width * 0.7//0.7//3.0;
self.itemSize = CGSizeMake(itemWidth, itemWidth);
self.sectionInset = UIEdgeInsetsMake(0,0,0,0);
self.minimumLineSpacing = 0
self.minimumInteritemSpacing = 0
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes:[UICollectionViewLayoutAttributes] = super.layoutAttributesForElementsInRect(rect)!
var visibleRect:CGRect = CGRect()
visibleRect.origin = self.collectionView!.contentOffset;
visibleRect.size = self.collectionView!.bounds.size;
for layoutAttributes in attributes {
if (CGRectIntersectsRect(layoutAttributes.frame, rect)) {
//we must align items to the bottom of the collection view on y axis
let frameHeight = self.collectionView!.bounds.size.height
layoutAttributes.center.y = frameHeight
layoutAttributes.center.x = layoutAttributes.center.x - self.itemSize.width/2
//find where ite, left x is
let itemLeftX:CGFloat = layoutAttributes.center.x
//distance of the item from the left of the viewport
let distanceFromTheLeft:CGFloat = itemLeftX - CGRectGetMinX(visibleRect)
let normalizedDistanceFromTheLeft = abs(distanceFromTheLeft) / self.collectionView!.frame.size.width
//item that is closer to the left is most visible one
layoutAttributes.alpha = 1 - normalizedDistanceFromTheLeft
layoutAttributes.zIndex = abs(Int(layoutAttributes.alpha)) * 10;
//scale items
let scale = min(layoutAttributes.alpha + 0.5, 1)
layoutAttributes.transform = CGAffineTransformMakeScale(scale, scale)
}
}
return attributes;
}
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// Snap cells to centre
var newOffset = CGPoint()
let layout = collectionView!.collectionViewLayout as! UICollectionViewFlowLayout
let width = layout.itemSize.width + layout.minimumLineSpacing
var offset = proposedContentOffset.x + collectionView!.contentInset.left
if velocity.x > 0 {
//ceil returns next biggest number
offset = width * ceil(offset / width)
} else if velocity.x == 0 { //6
//rounds the argument
offset = width * round(offset / width)
} else if velocity.x < 0 { //7
//removes decimal part of argument
offset = width * floor(offset / width)
}
newOffset.x = offset - collectionView!.contentInset.left
newOffset.y = proposedContentOffset.y //y will always be the same...
return newOffset
}
}
Answering my own question.
So as I suspected , the layout was taking an old center into account that is why I had to correct the center of the cell right after changing the anchor point . So my custom cell now looks like this :
class SongsCollectionViewCell : UICollectionViewCell {
#IBOutlet weak var imgAlbumCover: UIImageView!
override func prepareForReuse() {
imgAlbumCover.hnk_cancelSetImage()
imgAlbumCover.image = nil
}
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
//we must change the anchor point for propper cells positioning and scaling
self.layer.anchorPoint.x = 0
self.layer.anchorPoint.y = 1
//we need to adjust a center now
self.center.x = self.center.x - layoutAttributes.size.width/2
}
}
Hope it helps someone