如何在点击时展开多个 Collection View Cell

How to expand multiple UICollectionViewCells on tap

我正在尝试构建一个 collectionView,它可以在 selected/tapped 之后展开多个单元格,或者在取消 selected 时折叠多个单元格,当单元格保留在屏幕,但是一旦展开的单元格离开屏幕,我就会遇到意外行为。

例如,如果我 select 一个索引路径为 0 的单元格,然后向下滚动,点击索引路径为 8 的单元格,滚动回到索引路径为 0 的单元格(它已经折叠),我会点击它然后滚动回到 IndexPath 8 的单元格并再次点击它它会扩展 + IndexPath 10 的单元格也会扩展。

CollectionView 已以编程方式实现,UICollectionViewCell 已被子类化。

ViewController 持有 UICollectionView:

import UIKit

class CollectionViewController: UIViewController {

    // MARK: - Properties
    fileprivate var collectionView: UICollectionView!

    var manipulateIndex: NSIndexPath? {
        didSet {
            collectionView.reloadItems(at: collectionView.indexPathsForSelectedItems!)
        }
    }

    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()

        let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)

        collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "Cell")
        collectionView.backgroundColor = UIColor.white
        self.view.addSubview(collectionView)
    }

}

// MARK: - UICollectionViewDataSource
extension CollectionViewController: UICollectionViewDataSource {

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

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

        cell.textLabel.text = "\(indexPath.item)"
        cell.backgroundColor = UIColor.orange

        return cell
    }
}

// MARK: - UICollectionViewDelegate
extension CollectionViewController: UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
        let cell = collectionView.cellForItem(at: indexPath) as! CustomCell
        cell.expanded = !cell.expanded

        manipulateIndex = indexPath as NSIndexPath

        return false
    }
}

// MARK: - UICollectionViewDelegateFlowLayout
extension CollectionViewController: UICollectionViewDelegateFlowLayout {

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

        if let cell = collectionView.cellForItem(at: indexPath) as? CustomCell {
            if cell.expanded == true {
                return CGSize(width: self.view.bounds.width - 20, height: 300)
            }

            if cell.expanded == false {
                return CGSize(width: self.view.bounds.width - 20, height: 120.0)
            }
        }

        return CGSize(width: self.view.bounds.width - 20, height: 120.0)

    }
}

以及子类化的自定义 UICollectionViewCell:

import UIKit

class CustomCell: UICollectionViewCell {

    var expanded: Bool = false
    var textLabel: UILabel!

    override init(frame: CGRect) {
        super.init(frame: frame)

        textLabel = UILabel(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height/3))
        textLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
        textLabel.textAlignment = .center
        contentView.addSubview(textLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

请帮忙,非常感谢这位了不起的人,他可以帮助我! :)

UICollectionView 将为数据模型中的多个对象重复使用单元格。您无法控制在 reloadItems 期间重用哪些单元格 您不应假设给定单元格中的 expanded 状态对应于数据模型的状态。相反,您应该在数据模型中以某种方式保持 expanded 状态,并且在每次调用 cellForItemAt 时保持 re-setting 状态。

换句话说,将你的状态保存在你的模型中并在 cellForItemAt 中设置单元格状态,不要将它保存在单元格本身中。

试试这个:
示例 1:一次只展开一个单元格
注意:不需要在自定义单元格中使用扩展的布尔变量

var section:Int?
var preSection:Int?
var expand:Bool = false


extension ViewController: UICollectionViewDataSource {

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

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

        cell.textLabel.text = "\(indexPath.item)"
        cell.backgroundColor = UIColor.orange

        return cell
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        if (self.section != nil) {
            self.preSection = self.section
        }

        self.section = indexPath.row

        if self.preSection == self.section {
            self.preSection = nil
            self.section = nil
        }else if (self.preSection != nil) {
            self.expand = false
        }
        self.expand = !self.expand
        self.collectionView.reloadItems(at: collectionView.indexPathsForSelectedItems!)

    }

}


// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {

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

        if self.expand, let row = self.section, row == indexPath.row {
            return CGSize(width: self.view.bounds.width - 20, height: 300)
        }else{
            return CGSize(width: self.view.bounds.width - 20, height: 120.0)
        }

    }
}

示例 2: 展开多个单元格

import UIKit

class ViewController: UIViewController {


    // MARK: - Properties
    fileprivate var collectionView: UICollectionView!
    var expandSection = [Bool]()
    var items = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.items = ["A","B","C","D","E","F","G","H","J","K"]
        let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)

        collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "Cell")
        collectionView.backgroundColor = UIColor.white
        self.expandSection = [Bool](repeating: false, count: self.items.count)

        self.view.addSubview(collectionView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

extension ViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.items.count
    }

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

        cell.textLabel.text = self.items[indexPath.row]
        cell.backgroundColor = UIColor.orange

        return cell
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        self.expandSection[indexPath.row] = !self.expandSection[indexPath.row]
        self.collectionView.reloadItems(at: collectionView.indexPathsForSelectedItems!)
    }

}


// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {

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

        if self.expandSection[indexPath.row] {
            return CGSize(width: self.view.bounds.width - 20, height: 300)
        }else{
            return CGSize(width: self.view.bounds.width - 20, height: 120.0)
        }
    }
}