'In UICollectionView not obeying zIndex of UICollectionViewLayoutAttributes
I'm trying to make the top cell of the collection be behind all the other cells by the zIndex factor
┌──────────┐
│ │
│ Cell 0 │
│┌─────────┴┐
└┤ │
│ Cell 4 │
│ │
└──────────┘
┌──────────┐
│ │
│ Cell 5 │
│ │
└──────────┘
┌──────────┐
│ │
│ Cell 6 │
│ │
└──────────┘
In custom layout in method prepare I add Zindex as follows
override func prepare() {
super.prepare()
/// Some code
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.zIndex = zIndex
/// Some code
}
Next, I add the activation of this zIndex in UICollectionViewCell
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
layer.zPosition = CGFloat(layoutAttributes.zIndex)
}
When reusing a cell. The topmost cell, which should be in the background, becomes in the foreground
Tell me who faced this problem. What am I doing wrong 🙏
Solution 1:[1]
I was able to achieve what you wanted with a custom flow layout.
Here is the result with a regular flow layout
I create a custom flow layout class so that the cells could be in 1 column and the second cell overlaps the first
fileprivate class OverlapFlowLayout: UICollectionViewFlowLayout {
// Y Offset for the cells after the first
let initialYOffset: CGFloat = 30
// X Offset the cells after the first
let xOffset: CGFloat = 20
// Vertical spacing between cells
let cellSpacing: CGFloat = 10
// Store the updated content height
var contentHeight: CGFloat = .zero
// Cache layout attributes for the cells
private var cellLayoutCache: [IndexPath: UICollectionViewLayoutAttributes] = [:]
override func prepare() {
guard let collectionView = collectionView else { return }
// Only calculate if the cache is empty
guard cellLayoutCache.isEmpty else { return }
let sections = 0 ... collectionView.numberOfSections - 1
// Set the content height as the initial Y offset
// so that the second cell overlaps the first
contentHeight = initialYOffset
// Loop through all the sections
for section in sections {
let itemsInSection = collectionView.numberOfItems(inSection: section)
// Generate valid index paths for all items in the section
let indexPaths = [Int](0 ... itemsInSection - 1).map {
IndexPath(item: $0, section: section)
}
// Loop through all index paths and cache all the layout attributes
// so it can be reused later
for indexPath in indexPaths {
let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
var itemFrame: CGRect
// Check if it is the first item
if attribute.indexPath.item == 0 {
itemFrame = CGRect(x: 0,
y: 0,
width: itemSize.width,
height: itemSize.height)
}
else {
// Use the current content height as the y position
// so the items form one column
itemFrame = CGRect(x: 20,
y: contentHeight,
width: itemSize.width,
height: itemSize.height)
// Update the y offset by the cell height and spacing
contentHeight += itemSize.height + cellSpacing
}
// Set the frame for the current item
attribute.frame = itemFrame
cellLayoutCache[indexPath] = attribute
}
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// Retrieve the attributes from the cache
return cellLayoutCache[indexPath]
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Get the attributes that fall in the current view port
let itemAttributes
= cellLayoutCache.values.filter { rect.intersects($0.frame) }
return itemAttributes
}
override var collectionViewContentSize: CGSize {
guard let collectionView = collectionView else { return .zero }
// Update the content size with the new height
return CGSize(width: collectionView.bounds.width,
height: contentHeight)
}
}
This seems to work ok for the most part but if I scroll down and come back,the first (red) cell comes in front:
So then I just add the z index in prepare function to solve this
// Check if it is the first item
if attribute.indexPath.item == 0 {
itemFrame = CGRect(x: 0,
y: 0,
width: itemSize.width,
height: itemSize.height)
attribute.zIndex = -1
}
else {
// Use the current content height as the y position
// so the items form one column
itemFrame = CGRect(x: 20,
y: contentHeight,
width: itemSize.width,
height: itemSize.height)
// Update the y offset by the cell height and spacing
contentHeight += itemSize.height + cellSpacing
attribute.zIndex = 0
}
And now everything works as it should
The full example of this code can be found here
Solution 2:[2]
You should create your own UICollectionViewFlowLayout subclass and use the following method to set attributes:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
let backMostCellIndex = 0
guard let attributes = attributes else {
return nil
}
for attribute in attributes {
if attribute.indexPath.item == backMostCellIndex {
attribute.zIndex = 0
} else {
attribute.zIndex = 1
}
}
return attributes
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Shawn Frank |
| Solution 2 | Alex Shpilenia |



