iOS 14.3 上的 UICollectionViewCompositionalLayout 错误

问题描述

我在 iOS 14.3 上遇到了一个奇怪的布局问题,在我的情况下,集合视图使用了 UICollectionViewCompositionalLayoutUICollectionViewDiffableDataSource。 问题是当您有一个正交截面时,内部 _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
        }
    }
}

破坏布局的事情是将 .firstitemSize 都放入 groupSize 部分,高度为 .estimated

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

Broken layout iOS 14.3

运行完全相同的代码直到 iOS 14.2,您将获得正确的布局。

Correct Layout

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

谢谢

解决方法

我们有估计高度的标题,并且由于这种回归,一些带有 _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
        }
    }
}
,

对我来说,这个问题在 iOS 14.314.4 中是可重现的。现在已在 iOS 14.5 beta1

中修复

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

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

,

我们有一个稍微不同的用例,其中包含滚动视图的部分也使用估计高度,因此我修改了 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 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 版本:

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)