iOS 14.3 上的 UICollectionViewCompositionalLayout 错误

UICollectionViewCompositionalLayout bug on iOS 14.3

我在 iOS 14.3 上遇到一个奇怪的布局问题,集合视图使用 UICollectionViewCompositionalLayout 在我的例子中与 UICollectionViewDiffableDataSource. 问题是当你有一个正交部分前面有一个固有高度部分时,内部 _UICollectionViewOrthogonalScrollerEmbeddedScrollView 的错误位置。

幸运的是我能够很容易地重现这个问题。 考虑使用此数据源:

private var dataSource: UICollectionViewDiffableDataSource<Section, String>!

enum Section: Int, Hashable, CaseIterable {
    case first = 0
    case second = 1
}

为每个部分创建以下布局:

private extension Section {
    var section: NSCollectionLayoutSection {
        switch self {
        case .first:
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
            let section: NSCollectionLayoutSection = .init(group: group)
            return section
        case .second:
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(200), heightDimension: .absolute(200))
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
            let section: NSCollectionLayoutSection = .init(group: group)
            section.orthogonalScrollingBehavior = .continuous
            section.contentInsets = .init(top: 10, leading: 10, bottom: 10, trailing: 10)
            section.interGroupSpacing = 10
            return section
        }
    }
}

破坏布局的是 .first 部分 itemSizegroupSize 的高度 .estimated

你可以在iOS 14.3看到下面的结果:乍一看布局在视觉上是正确的,但你马上意识到它坏了,因为内部滚动视图在错误的位置。 这意味着水平滚动发生在蓝色区域是错误的。

运行 完全相同的代码 直到 iOS 14.2 你得到了正确的布局。

你怎么看待这个问题? 我是不是遗漏了什么或者可能是 UIKit 错误?

谢谢

我们有 header 的估计高度和一些 _UICollectionViewOrthogonalScrollerEmbeddedScrollView 的部分由于这种回归而完全损坏。所以这是一个适用于我们案例的解决方案

public final class CollectionView: UICollectionView {
    
    public override func layoutSubviews() {
        super.layoutSubviews()
    
        guard #available(iOS 14.3, *) else { return }
    
        subviews.forEach { subview in
            guard
                let scrollView = subview as? UIScrollView,
                let minY = scrollView.subviews.map(\.frame.origin.y).min(),
                minY > scrollView.frame.minY
            else { return }
    
            scrollView.contentInset.top = -minY
            scrollView.frame.origin.y = minY
        }
    }
}

我们有一个稍微不同的用例,其中带有滚动视图的部分也使用估计高度,所以我修改了 softenhard 的解决方案以同时调整滚动视图的高度。

public final class CollectionView: UICollectionView {
    override public func layoutSubviews() {
        super.layoutSubviews()

        guard #available(iOS 14.3, *) else { return }

        subviews.forEach { subview in
            guard
                let scrollView = subview as? UIScrollView,
                let minY = scrollView.subviews.map(\.frame.origin.y).min(),
                let maxHeight = scrollView.subviews.map(\.frame.height).max(),
                minY > scrollView.frame.minY || maxHeight > scrollView.frame.height
            else { return }

            scrollView.contentInset.top = -minY
            scrollView.frame.origin.y = minY
            scrollView.frame.size.height = maxHeight
        }
    }
}

对我来说,这个问题在 iOS 14.314.4 中可以重现。现在固定在 iOS 14.5 beta1

尝试安装最新的 Xcode 测试版 12.5 beta 并使用模拟器进行测试 运行 iOS 14.5

Xcode 测试版 link: https://developer.apple.com/download/

我遇到了同样的问题,其他答案的解决方法对我不起作用。

所以我做了这个扩展来确定它是否是 bug iOS-版本:

extension UICollectionView {
    static var isIosVersionWithSizeEstimationBug: Bool {
        if #available(iOS 14.5, *) {
            return false
        }
        if #available(iOS 14.3, *) {
            return true
        }
        return false
    }
}

在这种情况下,我使用它来使用绝对高度。它可能不是适用于每个用例的解决方法。但对我来说这是一个稳定的解决方案:

let height: NSCollectionLayoutDimension = {
    let maxPossibleHeight: CGFloat = 280
    if UICollectionView.isIosVersionWithSizeEstimationBug {
        return .absolute(maxPossibleHeight)
    } else {
        return .estimated(maxPossibleHeight)
    }
}()
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: height)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: height)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)