UICollectionViewCompositionalLayout 动态高度导致布局调整和闪烁

Aod*_*odh 5 ios uicollectionview uicollectionviewcompositionallayout

我正在使用 UICollectionViewCompositionalLayout 在我的 iOS 应用程序中创建水平滚动(正交滚动)列表的垂直滚动列表。

当我为项目和组指定估计高度时,当较高的项目滚动到视图中时,集合视图会调整其高度。

另一方面,如果我指定组的估计高度和项目的小数高度 1.0,则无论设置的约束和压缩阻力如何,高度似乎都会被视为绝对值。

这是我的 createSection 函数:

func createSection(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection {
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .absolute(90),
        heightDimension: .estimated(44)
    )
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    let groupSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(90 / layoutEnvironment.container.effectiveContentSize.width),
        heightDimension: .estimated(44)
    )
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
    let section = NSCollectionLayoutSection(group: group)
    section.interGroupSpacing = 16.0
    section.orthogonalScrollingBehavior = .continuous
    return section
}
Run Code Online (Sandbox Code Playgroud)

我尝试进行子类化UICollectionViewCompositionalLayout和重写,layoutAttributesForElementsInRect以根据每个部分中最高的项目来调整属性。然而,这导致滚动期间闪烁,我怀疑这是由于prepare在边界变化时被连续调用所致。

我事先不知道每个单元格中视图的高度。但是,我已确保它们受到正确约束,并且垂直轴上的压缩阻力设置为所需的值。

预期行为:每个单元格的高度根据其内容动态调整,当不同高度的单元格进入视图时,不会导致布局变化或闪烁。

实际行为:为项目和组指定估计高度会导致滚动时布局发生变化,而为项目设置分数高度会导致组的估计高度被视为绝对值。重写layoutAttributesForElementsInRect并在那里调整会导致闪烁。

如何处理 UICollectionViewCompositionalLayout 中的动态单元格高度而不导致这些布局问题?我将非常感谢任何建议或潜在的解决方案。

这是一个演示该问题的 Swift Playground:https://github.com/aodhol/CompositionalProblem/tree/main

显示估计尺寸太大和太小的屏幕截图:

估计太大了 估计太小

KFD*_*oom 1

在 iOS 世界中,UICollectionViewCompositionalLayout 的设计并不是为了在向组方向滚动时处理不同大小的单元格。设置正交滚动行为后,通常认为正交滚动组中的所有项目都具有相同的大小。

但别担心,我们有一个解决方法。我们可以将 UICollectionViewFlowLayout 用于内部列表,而不是对外部垂直列表和内部水平列表使用 UICollectionViewCompositionalLayout。这种方法非常灵活,可以处理可变的单元尺寸。但是,请记住,如果您有大量数据,这可能不是最高效的解决方案。

让我们像这样重构你的 createSection 函数:

func createSection(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection {
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .estimated(44)
    )
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    let groupSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .estimated(44)
    )
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
    let section = NSCollectionLayoutSection(group: group)
    section.interGroupSpacing = 16.0
    return section
}
Run Code Online (Sandbox Code Playgroud)

然后,在处理您的 时UICollectionViewDataSource,您将像往常一样出列并设置您的单元。但这里有一个转折点 - 在每个单元格内,您将UICollectionView使用 a 进行嵌套UICollectionViewFlowLayout

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

    let flowLayout = UICollectionViewFlowLayout()
    flowLayout.scrollDirection = .horizontal
    flowLayout.estimatedItemSize = CGSize(width: 90, height: 44)
    
    cell.nestedCollectionView.collectionViewLayout = flowLayout
    cell.nestedCollectionView.dataSource = self
    cell.nestedCollectionView.delegate = self
    cell.nestedCollectionView.reloadData()
    
    return cell
}
Run Code Online (Sandbox Code Playgroud)

最后,确保您的单元格可以处理嵌套UICollectionView

class MyCell: UICollectionViewCell {
    static let identifier = "MyCell"
    
    let nestedCollectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        return collectionView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.addSubview(nestedCollectionView)
        
        NSLayoutConstraint.activate([
            nestedCollectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            nestedCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            nestedCollectionView.topAnchor.constraint(equalTo: contentView.topAnchor),
            nestedCollectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案应该提供您正在寻找的行为,但请记住它需要管理两个集合视图。此外,您可能会遇到一些需要解决的其他挑战。如果你有大量的单元,这个解决方法可能会慢一点,所以不能随意使用它。