UICollectionViewListCell 实现中不可预测的折叠/展开行为

问题描述 投票:0回答:1

我使用

UICollectionViewListCell
和 Diffable 数据源实现了折叠/展开功能。

但是,由此产生的行为是不可预测的。当点击一行时,它应该展开,而其他行则折叠,但这种情况并不一致。请参阅下面的视频了解更多背景信息。

enter image description here

这是我的源代码:

ViewController.swift

import UIKit

enum Section : Codable {
    case note
}

extension UIView {
    static func getUINib() -> UINib {
        return UINib(nibName: String(describing: self), bundle: nil)
    }
}

class ViewController: UIViewController {
    private let strings = [
        "Helo world",
        "Good bye world",
        "Hotdog",
        "Egg",
        "The word collapse has multiple meanings, including:",
        "A very loooooong text. A very loooooong text. A very loooooong text. A very loooooong text. A very loooooong text. A very loooooong text. ",
        "short text"
        
    ]
    
    private var selectedIndexPath: IndexPath?
    
    lazy var cellRegistration = UICollectionView.CellRegistration<DemoListCell, String>(cellNib: DemoListCell.getUINib()) { [weak self] (demoListCell, indexPath, string) in
        
        guard let self = self else { return }
        
        if indexPath == self.selectedIndexPath {
            print(">>>> expand - \(string)")
            demoListCell.expand()
        } else {
            print(">>>> collapse - \(string)")
            demoListCell.collapse()
        }
        demoListCell.label.text = string
    }
    
    private typealias DataSource = UICollectionViewDiffableDataSource<Section, String>
    private typealias Snapshot = NSDiffableDataSourceSnapshot<Section, String>

    private var dataSource: DataSource!
    
    @IBOutlet weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.collectionViewLayout = layoutConfig()
        
        collectionView.delegate = self
        
        dataSource = makeDataSource()
        
        var snapshot = Snapshot()
        snapshot.appendSections([.note])
        snapshot.appendItems(strings, toSection: .note)
        dataSource.apply(snapshot, animatingDifferences: true)
    }

    private func layoutConfig() -> UICollectionViewCompositionalLayout {
        let layout = UICollectionViewCompositionalLayout { section, layoutEnvironment in
            var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)

            config.headerMode = .none
            config.footerMode = .none
            config.showsSeparators = true
            config.headerTopPadding = 0
            
            config.backgroundColor = nil
            
            let layoutSection = NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
            
            return layoutSection
        }
        
        return layout
    }
    
    private func makeDataSource() -> DataSource {
        let cellRegistration = self.cellRegistration
        
        let dataSource = DataSource(
            collectionView: collectionView,
            cellProvider: { [weak self] (collectionView, indexPath, item) -> UICollectionViewCell? in
                guard let self = self else { return nil }
                
                return collectionView.dequeueConfiguredReusableCell(
                    using: cellRegistration,
                    for: indexPath,
                    item: item
                )
            }
        )
        
        return dataSource
    }
}

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        self.selectedIndexPath = indexPath
        
        var snapshot = dataSource.snapshot()
        
        snapshot.reconfigureItems(snapshot.itemIdentifiers)

        print(">>>> === apply ===")
        dataSource.apply(snapshot, animatingDifferences: true) {
            collectionView.collectionViewLayout.invalidateLayout()
        }
        
        collectionView.deselectItem(at: indexPath, animated: true)
    }
}

DemoListCell.swift

class DemoListCell: UICollectionViewListCell {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var extraView: UIView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    func expand() {
        extraView.isHidden = false
    }
    
    func collapse() {
        extraView.isHidden = true
    }
}

这是完整的可行源代码 - https://github.com/yccheok/expand-collapse

你知道我该如何解决这个问题吗?谢谢。

ios swift uikit
1个回答
0
投票

在这种情况下,不要显示/隐藏视图,而是使用两个不同的单元格,如下所示。

class ExpandCell: UICollectionViewListCell {
    private var label: UILabel!
    private var imageView: UIImageView!
    private var extraView: UIView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setData(_ title: String) {
        label.text = title
    }
    
    private func setup() {
        label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(label)
        
        imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = UIImage(systemName: "chevron.down")
        imageView.contentMode = .scaleAspectFit
        contentView.addSubview(imageView)
        
        extraView = UIView()
        extraView.translatesAutoresizingMaskIntoConstraints = false
        extraView.backgroundColor = .red
        contentView.addSubview(extraView)
        
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            
            imageView.leadingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor, constant: 8),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            imageView.centerYAnchor.constraint(equalTo: label.centerYAnchor),
            imageView.heightAnchor.constraint(equalToConstant: 18),
            imageView.widthAnchor.constraint(equalToConstant: 18),
            
            extraView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 8),
            extraView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            extraView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            extraView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            extraView.heightAnchor.constraint(equalToConstant: 100)
        ])
    }
}

class CollapseCell: UICollectionViewListCell {
    private var label: UILabel!
    private var imageView: UIImageView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setData(_ title: String) {
        label.text = title
    }
    
    private func setup() {
        label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(label)
        
        imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = UIImage(systemName: "chevron.down")
        imageView.contentMode = .scaleAspectFit
        contentView.addSubview(imageView)
        
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
            
            imageView.leadingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor, constant: 8),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            imageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            imageView.heightAnchor.constraint(equalToConstant: 18),
            imageView.widthAnchor.constraint(equalToConstant: 18)
        ])
    }
}

enum Section : Codable {
    case note
}

struct Item: Hashable {
    let text: String
    var isExpanded: Bool
}

private typealias DataSource = UICollectionViewDiffableDataSource<Section, Item>
private typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Item>

class ViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!
    
    private var items: [Item] = [
        .init(text: "Hello world", isExpanded: false),
        .init(text: "Good bye world", isExpanded: false),
        .init(text: "Hotdog", isExpanded: false),
        .init(text: "Egg", isExpanded: false),
        .init(text: "The word collapse has multiple meanings, including:", isExpanded: false),
        .init(text: "A very loooooong text. A very loooooong text. A very loooooong text. A very loooooong text. A very loooooong text. A very loooooong text. ", isExpanded: false),
        .init(text: "short text", isExpanded: false)
    ]
    
    private var selectedIndexPath: IndexPath?
    
    lazy var collapseCellRegistration = UICollectionView.CellRegistration<CollapseCell, Item> { cell, _, item in
        cell.setData(item.text)
    }
    
    lazy var expandCellRegistration = UICollectionView.CellRegistration<ExpandCell, Item> { cell, _, item in
        cell.setData(item.text)
    }
    
    private var dataSource: DataSource!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.collectionViewLayout = layoutConfig()
        collectionView.delegate = self
        dataSource = makeDataSource()
        updateDataSource()
    }
    
    private func layoutConfig() -> UICollectionViewCompositionalLayout {
        UICollectionViewCompositionalLayout { section, layoutEnvironment in
            var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
            config.headerMode = .none
            config.footerMode = .none
            config.showsSeparators = true
            config.headerTopPadding = 0
            config.backgroundColor = nil
            
            return NSCollectionLayoutSection.list(
                using: config,
                layoutEnvironment: layoutEnvironment
            )
        }
    }
    
    private func makeDataSource() -> DataSource {
        let collapseCellRegistration = self.collapseCellRegistration
        let expandCellRegistration = self.expandCellRegistration
        
        return DataSource(
            collectionView: collectionView,
            cellProvider: { collectionView, indexPath, item in
                if item.isExpanded {
                    collectionView.dequeueConfiguredReusableCell(
                        using: expandCellRegistration,
                        for: indexPath,
                        item: item
                    )
                } else {
                    collectionView.dequeueConfiguredReusableCell(
                        using: collapseCellRegistration,
                        for: indexPath,
                        item: item
                    )
                }
            }
        )
    }
}

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        items[indexPath.row].isExpanded = !items[indexPath.row].isExpanded
        if let selectedIndexPath = selectedIndexPath, selectedIndexPath != indexPath {
            items[selectedIndexPath.row].isExpanded = false
        }
        
        selectedIndexPath = indexPath
        updateDataSource()
        collectionView.deselectItem(at: indexPath, animated: true)
    }
    
    func updateDataSource() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.note])
        snapshot.appendItems(items, toSection: .note)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.