swiftuicollectionviewuicollectionviewlayout

How to scale and show next/previous items in a Collection View?


I'm working on a collection view that should look like this:

enter image description here

The scroll view works but I want to make my collection view peak the next item like in the screenshot, and when scrolling it scales the normal size of course.

I couldn't manage to do that with only enabling paging (of course).

Thank you all for any help!

Edit: This kinda helps what I want to achieve, but it still lacks scaling and does not work near good while scrolling.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return -100
    }

Solution

  • If you want to tweak the behaviour of item sizing inside UICollectionView you should subclass UICollectionViewLayout or UICollectionViewFlowLayout class and add the sizing behaviour inside collectionView(_:layout:sizeForItemAt:).

    The following examples expects that UICollectionView aspect ratio is 1:0.7 and UICollectionViewCell ratio is 3:4. Layout is subclassed from UICollectionViewFlowLayout class. Below code then down scales the item based on the distance from the centre.

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let collectionView = self.collectionView else { return nil }
    
        // Get the array of layout attributes from the superclass, which will be the starting point for modification.
        let attributesArray = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }
    
        // Calculate the center of the visible rect to use as a reference point for scaling.
        let center = collectionView.contentOffset.x + (collectionView.bounds.width / 2.0)
    
        // Iterate over the layout attributes array and adjust the attributes of cells within the visible rect.
        attributesArray?.forEach { attributes in
            // Ensure we're only adjusting attributes for cells that are visible.
            if attributes.frame.intersects(rect) {
                // Determine how far the cell's center is from the center of the visible rect.
                let distanceFromCenter = abs(center - attributes.center.x)
                // Define the point at which cells no longer scale.
                let maxDistance = (collectionView.bounds.width / 2) + (attributes.size.width / 2)
                // Normalize the distance to a factor between 0 and 1.
                let normalizedDistance = min(distanceFromCenter / maxDistance, 1)
                // Define the minimum scale factor (e.g., no cell will be smaller than 70% of its original size).
                let minimumScaleFactor: CGFloat = 0.7
                // Calculate the scale factor based on the cell's distance from the center.
                let scaleFactor = 1 - (1 - minimumScaleFactor) * normalizedDistance
                // Apply a 3D scale transform to the cell's attributes.
                attributes.transform3D = CATransform3DMakeScale(scaleFactor, scaleFactor, 1)
                // Ensure that cells closer to the center appear above others.
                attributes.zIndex = Int(scaleFactor * 10)
            }
        }
    
        return attributesArray
    }
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        // Invalidate the layout on bounds changes to recalculate scaling for new cell positions.
        return true
    }
    

    This will make effect as shown on the following image, but without paging. For getting paging to work you should also override targetContentOffset(forProposedContentOffset:withScrollingVelocity:) method on the UICollectionViewFlowLayout subclass and apply appropriate logic to it. Scaled item UICollectionView