Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a particular cell of an iOS collectionView fade out as the collectionView scrolls?

I want to make all the right side cells of my UICollectionView fade out as they scroll similar to Apple's messages app but not effect the color or transparency of the other cells in the collectionView. Is there a way to adjust the transparency of a UICollectionViewCell based on it's scroll position to achieve that effect?

like image 729
alionthego Avatar asked Jun 21 '16 10:06

alionthego


People also ask

What is difference between Tableview and Collectionview?

When would you choose to use a collection view rather than a table view? Suggested approach: Collection views are there to display grids, but also handle entirely custom layouts, whereas table views are simple linear lists with headers and footers.

How do I remove cells from Collectionview?

Run Project. Build and Run the project and select the Edit Button. Select a few cells and press the Trash button to remove the items.

Do Collectionview use cells?

Like a table view, a collection view is a UIScrollView subclass. UICollectionViewCell: This is similar to a UITableViewCell in a table view. These cells make up the view's content and are subviews to the collection view. You can create cells programmatically or inside Interface Builder.

What is the use of datasource and delegate in Uicollectionview in IOS?

This method asks the data source object to provide a supplementary view to display in the collection view. It asks the datasource object whether the specified item can be moved to another location in the collectionview. This method moves the specified item to the specified indexpath.


2 Answers

You can do a lot of fun stuff to collection views. I like to subclass UICollectionViewFlowLayout. Here is an example that fades the top and the bottom of the collection view based on distance from center. I could modify it to fade only the very edges but you should figure it after you look through the code.

import UIKit

class FadingLayout: UICollectionViewFlowLayout,UICollectionViewDelegateFlowLayout {

    //should be 0<fade<1
    private let fadeFactor: CGFloat = 0.5
    private let cellHeight : CGFloat = 60.0

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    init(scrollDirection:UICollectionViewScrollDirection) {
        super.init()
        self.scrollDirection = scrollDirection

    }

    override func prepare() {
        setupLayout()
        super.prepare()
    }

    func setupLayout() {
        self.itemSize = CGSize(width: self.collectionView!.bounds.size.width,height:cellHeight)
        self.minimumLineSpacing = 0
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }

    func scrollDirectionOver() -> UICollectionViewScrollDirection {
        return UICollectionViewScrollDirection.vertical
    }
    //this will fade both top and bottom but can be adjusted
    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
                }
            }
            return attributes
        }else{
            return nil
        }
    }
    //appear and disappear at 0
    override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItem(at: itemIndexPath)! as UICollectionViewLayoutAttributes
        attributes.alpha = 0
        return attributes
    }

    override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItem(at: itemIndexPath)! as UICollectionViewLayoutAttributes
        attributes.alpha = 0
        return attributes
    }
}

And in your setup in your controller with the collection view it would look like this.

let layout = FadingLayout(scrollDirection: .vertical)
collectionView.delegate = self
collectionView.dataSource = self
self.collectionView.setCollectionViewLayout(layout, animated: false)

I can tell you how to modify it if I knew the use case a bit better.

like image 183
agibson007 Avatar answered Sep 25 '22 17:09

agibson007


This is quite simple if you subclass UICollectionViewFlowLayout. First thing you'll need to do is make sure the visible attributes are recalculated when bounds change/scroll happens by returning true in

shouldInvalidateLayout(forBoundsChange newBounds: CGRect)

Then in layoutAttributesForElements(in rect: CGRect) delegate call, get the attributes calculated by the super class and modify the alpha value based on the offset of the item in the visible bounds, thats it. Distinguishing between left/right side items can be handled in the controller with whatever logic you have and communicated to the layout class to avoid applying this effect on left side items. (I used ´CustomLayoutDelegate´ for that which is implemented in the controller that simply identifies items with odd indexPath.row as left side cells)

Here is a demo that applies this effect on items with with even indexPath.row skipping odd rows

import UIKit

class ViewController: UIViewController {

    /// Custom flow layout
    lazy var layout: CustomFlowLayout = {
        let l: CustomFlowLayout = CustomFlowLayout()
        l.itemSize = CGSize(width: self.view.bounds.width / 1.5, height: 100)
        l.delegate = self

        return l
    }()

    /// The collectionView if you're not using UICollectionViewController
    lazy var collectionView: UICollectionView = {
        let cv: UICollectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: self.layout)
        cv.backgroundColor = UIColor.lightGray

        cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
        cv.dataSource = self

        return cv
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
    }

}

extension ViewController: UICollectionViewDataSource, CustomLayoutDelegate {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 30
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = UIColor.black

        return cell
    }

    // MARK: CustomLayoutDelegate

    func cellSide(forIndexPath indexPath: IndexPath) -> CellSide {

        // TODO: Your implementation to distinguish left/right indexPath

        // Even rows are .right and Odds .left
        if indexPath.row % 2 == 0 {
            return .right
        } else {
            return .left
        }
    }
}

public enum CellSide {
    case right
    case left
}

protocol CustomLayoutDelegate: class {

    func cellSide(forIndexPath indexPath: IndexPath) -> CellSide
}

class CustomFlowLayout: UICollectionViewFlowLayout {

    /// Delegates distinguishing between left and right items
    weak var delegate: CustomLayoutDelegate!

    /// Maximum alpha value
    let kMaxAlpha: CGFloat = 1

    /// Minimum alpha value. The alpha value you want the first visible item to have
    let kMinAlpha: CGFloat = 0.3

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let cv = collectionView, let rectAtts = super.layoutAttributesForElements(in: rect) else { return nil }

        for atts in rectAtts {

            // Skip left sides
            if delegate.cellSide(forIndexPath: atts.indexPath) == .left {
                continue
            }

            // Offset Y on visible bounds. you can use
            //      ´cv.bounds.height - (atts.frame.origin.y - cv.contentOffset.y)´
            // To reverse the effect
            let offset_y = (atts.frame.origin.y - cv.contentOffset.y)

            let alpha = offset_y * kMaxAlpha / cv.bounds.height

            atts.alpha = alpha + kMinAlpha
        }

        return rectAtts
    }

    // Invalidate layout when scroll happens. Otherwise atts won't be recalculated
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }

}
like image 33
Lukas Avatar answered Sep 22 '22 17:09

Lukas