Tobias
Tobias

Reputation: 578

Expandable UICollectionViewCell

I'm trying to program an expandable UICollectionViewCell. If the user presses the topbutton, the cell should expand to a different height and reveal the underlying content.

My first implementation features a custom UICollectionViewCell, which can be expanded by clicking the topbutton, but the collectionview does not expand too. Atm. the cell is the last one in the collectionview and just by swiping down enough, you can see the expanded content. If you stop touching, the scrollview jumps up and nothing of the expanded cell could be seen.

Here's my code:

import Foundation
import UIKit

class ExpandableCell: UICollectionViewCell {

  @IBOutlet weak var topButton: IconButton!

  public var expanded : Bool = false

  private var expandedHeight : CGFloat = 200

  private var notExpandedHeight : CGFloat = 50

  @IBAction func topButtonTouched(_ sender: AnyObject) {
    if (expanded == true) {
      self.deExpand()
    } else {
      self.expand()
    }
  }

  public func expand() {
   UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
      self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width, height: self.expandedHeight)
    }, completion: { success in
      self.expanded = true
    })
  }

  public func deExpand() {
    UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
      self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width, height: self.notExpandedHeight)
    }, completion: { success in
      self.expanded = false
    })
  }
}

I also have problems implementing the following method correctly, because I haven't got a direct reference to the cell:

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize

Basicly everything should be animated.

The screenshot shows an expanded and not expanded cell. Hope that helps!

opened and closed cell

Thanks!

Upvotes: 6

Views: 16096

Answers (4)

Jevon718
Jevon718

Reputation: 454

When using a UICollectionViewDiffableDataSource iOS 15

let dataSource: UICollectionViewDiffableDataSource<Section, Item>

var sectionSnapshot = dataSource.snapshot(for: section)
sectionSnapshot.expand([section])
dataSource.apply(sectionSnapshot, to: section, animatingDifferences: false)

And if collapsing

var sectionSnapshot = dataSource.snapshot(for: section)
sectionSnapshot.collapse([section])
dataSource.apply(sectionSnapshot, to: section, animatingDifferences: false)

I will also add you need an understanding of how to setup an initial snapshot to begin with and that goes past the scope of this question.

Upvotes: 1

YodagamaHeshan
YodagamaHeshan

Reputation: 6520

You can use header to show non-expand cell. And when you two a button or something in it , then you can insert other part (expanding part ) as a row in that section which the header belongs to.

Benefits - you can use many provided animations when the expanding happen.

Upvotes: 0

Samip Shah
Samip Shah

Reputation: 874

If you have more than cell that needs to be expanded to different height when a button is touched inside the collection view cell, then, here is the code.

I have used the delegate pattern to let the controller know, button of which cell was touched using the indexPath. You need to pass the indexpath of the cell to the cell when creating the cell.

when the button is touched the cell calls the delegate(ViewController), which updates the isExpandedArray accordingly and reloads the particular cell.

CollectionViewCell

 protocol ExpandedCellDelegate:NSObjectProtocol{
    func topButtonTouched(indexPath:IndexPath)
}

class ExpandableCell: UICollectionViewCell {

    @IBOutlet weak var topButton: UIButton!
    weak var delegate:ExpandedCellDelegate?

    public var indexPath:IndexPath!

    @IBAction func topButtonTouched(_ sender: UIButton) {
        if let delegate = self.delegate{
            delegate.topButtonTouched(indexPath: indexPath)
        }
    }
}

View Controller Class

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    var expandedCellIdentifier = "ExpandableCell"

    var cellWidth:CGFloat{
        return collectionView.frame.size.width
    }
     var expandedHeight : CGFloat = 200
     var notExpandedHeight : CGFloat = 50

     var dataSource = ["data0","data1","data2","data3","data4"]
     var isExpanded = [Bool]()

    override func viewDidLoad() {
        super.viewDidLoad()
        isExpanded = Array(repeating: false, count: dataSource.count)
    }
}


extension ViewController:UICollectionViewDataSource{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return dataSource.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
          let cell = collectionView.dequeueReusableCell(withReuseIdentifier: expandedCellIdentifier, for: indexPath) as! ExpandableCell
            cell.indexPath = indexPath
            cell.delegate = self
            //configure Cell
            return cell

    }
}

extension ViewController:UICollectionViewDelegateFlowLayout{
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        if isExpanded[indexPath.row] == true{
             return CGSize(width: cellWidth, height: expandedHeight)
        }else{
            return CGSize(width: cellWidth, height: notExpandedHeight)
        }

    }

}

extension ViewController:ExpandedCellDelegate{
    func topButtonTouched(indexPath: IndexPath) {
        isExpanded[indexPath.row] = !isExpanded[indexPath.row]
        UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
              self.collectionView.reloadItems(at: [indexPath])
            }, completion: { success in
                print("success")
        })
    }
}

Upvotes: 29

Mercurial
Mercurial

Reputation: 2165

Don't change the cell frame directly. Implement func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize and handle heights there.

Lets say you have one cell in your collectionView and a top button above it:

let expanded = false

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
if expanded{
   // what is the size of the expanded cell?
   return collectionView.frame.size 
}else{
    // what is the size of the collapsed cell?
   return CGSizeZero
}
}

@IBAction func didTouchUpInsideTopButton(sender: AnyObject){
    // top button tap handler
    self.expanded = !self.expanded
    // this call will reload the cell and make it redraw (animated) Since the expanded flag changed, the sizeForItemAt call above will return a different size and animate the size change
    self.collectionView.reloadItemsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)])
}

Upvotes: 1

Related Questions