Search code examples
swiftanimationstackcollectionvieweffect

How to create effect of stack of view for collection view?


I am try create effect like few view stack over other for collection view, I am not build effect like this in the past and can't understand how I can create this effect programmatically without images?

Can somebody explain me how I can repeat this effect or post link to code of tutorial?

Example of this effect below:

stack effect


Solution

  • To get that effect you'd need to add layers under the collection view cells' layers that are smaller and shifted down a little from the cell's layers. You'd use the same background color as the cell on each layer that had a lower alpha than the cell's layer.

    I created a sample project that demonstrates how to get the effect:

    https://github.com/DuncanMC/CustomCollectionViewCell.git

    The cells look like this:

    enter image description here

    The heavy lifting is in a custom subclass of UICollectionViewCell that I called MyCollectionViewCell

    Here is that class:

    class MyCollectionViewCell: UICollectionViewCell {
    
        public var contentCornerRadius: CGFloat = 0 {
            didSet {
                contentView.layer.cornerRadius = contentCornerRadius
                sizeLayerFrames()
            }
    
        }
    
        public var fraction: CGFloat = 0.075 { // What percent to shrink & shift the faded layers down (0.075 = 7.5%)
            didSet {
                sizeLayerFrames()
            }
        }
        private var layerMask =   CAShapeLayer()
        private var layer1 = CALayer()
        private var layer2 = CALayer()
    
    
        // Use this function to set the cell's background color.
        // (You can't set the view's background color, since we Don't clip the view to it's bounds.)
    
        // Be sure to set the background color explicitly, since by default it sets a random color that will persist
        // as the cells are recylced, causing your cell colors to move around as the user scrolls
        public func setBackgroundColor(_ color: UIColor)  {
            var red: CGFloat = 0
            var green: CGFloat = 0
            var blue: CGFloat = 0
            color.getRed(&red, green: &green, blue: &blue, alpha: nil)
            contentView.layer.backgroundColor = color.cgColor
    
            //Make the first extra layer have the same color as the cell's layer, but with alpha 0.25
            layer1.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.25).cgColor
    
            //Make the second extra layer have the same color as the cell's layer, but with alpha 0.125
            layer2.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 0.125).cgColor
        }
    
        @IBOutlet weak var customLabel: UILabel!
    
        //Do The initial setup once the cell is loaded.
        //Note that t
        override func awakeFromNib() {
            contentView.layer.masksToBounds = false
    
            // Color each cell's layer some random hue (change to set whatever color you desire.)
            //For testing, use a color based on a random hue and fairly high random brightness.
            let hue = CGFloat.random(in: 0...360)
            let brightness = CGFloat.random(in: 0.8...1.0)
            layer.masksToBounds = false
            setBackgroundColor(UIColor(hue: hue, saturation: 1, brightness: brightness, alpha: 1))
    
    
            // Make the inside of the shape layer white (opaque), The color doesn't matter - just that the alpha value is 1
            // and the outside clear (transparent)
            layerMask.fillColor = UIColor.white.cgColor
            layerMask.backgroundColor = UIColor.clear.cgColor
    
            //With the even/odd rule, the inner shape will not be filled (we'll only fill the part NOT in the inner shape)
            layerMask.fillRule = .evenOdd
            contentCornerRadius = 30
    
    
            sizeLayerFrames()
    
            contentView.layer.addSublayer(layer1)
            layer1.addSublayer(layer2)
            layer1.mask = layerMask
        }
    
        private func sizeLayerFrames() {
            layer1.cornerRadius = contentCornerRadius
            layer2.cornerRadius = contentCornerRadius
    
    
            let viewBounds = bounds //Use the layer's bounds as the starting point for the extra layers.
            var frame1 = viewBounds
            frame1.origin.y += viewBounds.size.height * fraction
            frame1.origin.x += viewBounds.size.width * fraction
    
            frame1.size.width *= CGFloat(1 - 2 * fraction)
            layer1.frame = frame1
            var frame2 = viewBounds
            frame2.origin.y += viewBounds.size.height * 0.75 * fraction
            frame2.origin.x += viewBounds.size.width  * fraction
    
            frame2.size.width *= CGFloat(1 - 4 * fraction)
            layer2.frame = frame2
    
            //Create a mask layer to clip the extra layers.
            var maskFrame = viewBounds
    
            //We are going to install the mask on layer1, so offeset the frame to cover the whole view contents
            maskFrame.origin.y -= viewBounds.size.height * fraction
            maskFrame.origin.x -= viewBounds.size.width * fraction
            maskFrame.size.height += viewBounds.size.height * fraction * 1.75
            layerMask.frame = maskFrame
            maskFrame = viewBounds
            let innerPath = UIBezierPath(roundedRect: maskFrame, cornerRadius: 30)
            maskFrame.size.height += viewBounds.size.height * fraction * 1.75
            let combinedPath = UIBezierPath(rect: maskFrame)
            combinedPath.append(innerPath)
            layerMask.path = combinedPath.cgPath
        }
    
        override var bounds: CGRect {
            didSet {
                sizeLayerFrames()
            }
        }
    }