我使用
UICollectionViewListCell
和 Diffable 数据源实现了折叠/展开功能。
但是,由此产生的行为是不可预测的。当点击一行时,它应该展开,而其他行则折叠,但这种情况并不一致。请参阅下面的视频了解更多背景信息。
这是我的源代码:
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)
}
}
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
你知道我该如何解决这个问题吗?谢谢。
在这种情况下,不要显示/隐藏视图,而是使用两个不同的单元格,如下所示。
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)
}
}