skip to Main Content

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 🙏

2

Answers


  1. I was able to achieve what you wanted with a custom flow layout.

    Here is the result with a regular flow layout

    UICollectionViewFlowLayout custom swift iOS

    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:

    Custom UICollectionViewLayout iOS Swift overlap cells

    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

    UICollectionViewFlowLayout iOS Swift overlapping cells

    The full example of this code can be found here

    Login or Signup to reply.
  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
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search