53

类似这样的代码

collectionLayout.estimatedItemSize = CGSize(width: 50, height: 25)
collectionLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
collectionLayout.minimumInteritemSpacing = 10 

for _ in 0 ..< 1000 {
    let length = Int(arc4random() % 8)
    let string = randomKeyByBitLength(length)
    array.append(string!)
}
collectionView.reloadData()

单元格约束:

在此处输入图像描述

当我在 iOS 12 上运行它时,情况有所不同。左边模拟器是iOS 11,右边是iOS 12:

在此处输入图像描述

但是,当我滚动它时,单元格的框架将是正常的。


重现该问题的示例项目:https ://github.com/Coeur/StackOverflow51375566

4

8 回答 8

59

对于所有解决方案,请注意无需显式调用:它会自动发生reloadDataviewDidLoad

解决方案 1

灵感来自Samantha 的想法invalidateLayout异步 in viewDidLoad.

override func viewDidLoad() {
    super.viewDidLoad()

    //[...]

    for _ in 0 ..< 1000 {
        array.append(randomKeyByBitLength(Int(arc4random_uniform(8)))!)
    }

    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

解决方案 2

(不完善,见 DHennessy13 改进)

基于彼得拉皮苏的回答invalidateLayoutviewWillLayoutSubviews.

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    collectionView.collectionViewLayout.invalidateLayout()
}

正如 DHennessy13 所指出的,这个当前的解决方案viewWillLayoutSubviews并不完美,因为它会在旋转屏幕时使布局无效。

您可以关注DHennessy13关于此解决方案的改进。

解决方案 3

基于Tyler Sheaffer 的回答Shawn Aukstak 移植到 Swift和 Samantha 的想法。将您的 CollectionView 子类化以invalidateLayoutlayoutSubviews.

class AutoLayoutCollectionView: UICollectionView {

    private var shouldInvalidateLayout = false

    override func layoutSubviews() {
        super.layoutSubviews()
        if shouldInvalidateLayout {
            collectionViewLayout.invalidateLayout()
            shouldInvalidateLayout = false
        }
    }

    override func reloadData() {
        shouldInvalidateLayout = true
        super.reloadData()
    }
}

这个解决方案很优雅,因为它不需要更改您的 ViewController 代码。我已经在这个示例项目https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView的分支 AutoLayoutCollectionView 上实现了它。

解决方案 4

重写 UICollectionViewCell 默认约束。见拉里的回答

解决方案 5

实施collectionView(_:layout:sizeForItemAt:)并返回cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)。见马特回答

于 2018-09-03T11:06:27.320 回答
31

问题是这里提出的特性——根据 UICollectionViewFlowLayout 中的内部约束来调整自身大小的集合视图单元格——不存在。它从未存在过。苹果声称它确实如此,但事实并非如此。自从引入集合视图并首次提出此声明以来,我每年都对此提出错误;而且我的错误报告从未关闭,因为错误是真实的。不存在自调整大小的集合视图单元格之类的东西。

另请参阅我的答案:https ://stackoverflow.com/a/51585910/341994

几年来,尝试使用自定尺寸单元失败了。在其他年份,它不会崩溃,但会导致布局错误。但它不起作用

做这种事情的唯一方法是实现委托方法并自己sizeForItemAt提供大小。您可以通过调用轻松做到这一点

cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)

在您预先配置的模型单元上。这就是运行时应该为您做的事情——但事实并非如此。

所以这是我对原始问题的解决方案。我们生成了一个字符串大小对(作为元组)的数组,而不是一个简单的字符串数组。然后:

override func collectionView(_ collectionView: UICollectionView, 
    cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MyCell
        cell.label.text = self.array[indexPath.row].0
        return cell
}

func collectionView(_ collectionView: UICollectionView, 
    layout collectionViewLayout: UICollectionViewLayout, 
    sizeForItemAt indexPath: IndexPath) -> CGSize {
        return self.array[indexPath.row].1
}
于 2018-09-20T15:32:59.503 回答
29

这是另一个适用于Cœur的代码示例的解决方案,也适用于我的特殊情况,而其他答案没有。下面的代码替换了之前的CollectionViewCell子类实现ViewController.swift

class CollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var label: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()

        contentView.translatesAutoresizingMaskIntoConstraints = false

        let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor)
        let rightConstraint = contentView.rightAnchor.constraint(equalTo: rightAnchor)
        let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor)
        let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
    }
}

这受到来自UICollectionViewFlowLayout的ale84的回答的启发

于 2018-09-18T20:43:05.993 回答
12

我有同样的问题,单元格使用估计的大小(而不是自动大小)直到滚动。使用 Xcode 9.x 构建的相同代码在 iOS 11 和 12 上运行良好,并且在 Xcode 10 中构建它在 iOS 11 但不是 iOS 12 上正常运行。

到目前为止,我发现解决此问题的唯一方法是使集合视图的布局无效viewDidAppear,这可能会导致一些跳跃,或者在内部的异步块中viewWillAppear(不确定该解决方案的可靠性如何)。

override func viewDidLoad() {
    super.viewDidLoad()
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.estimatedItemSize = CGSize(width: 50, height: 50)
    layout?.itemSize = UICollectionViewFlowLayout.automaticSize
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // The following block also "fixes" the problem without jumpiness after the view has already appeared on screen.
    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // The following line makes cells size properly in iOS 12.
    collectionView.collectionViewLayout.invalidateLayout()
}
于 2018-08-23T12:27:11.647 回答
6

Cœur的解决方案 2可防止布局在用户面前闪烁或更新。但是,当您旋转设备时,它可能会产生问题。我在 viewWillLayoutSubviews 中使用变量“shouldInvalidateLayout”,并在 viewDidAppear 中将其设置为 false。

private var shouldInvalidateLayout = true

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    shouldInvalidateLayout = false
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    if shouldInvalidateLayout {
        collectionView.collectionViewLayout.invalidateLayout()
    }
}
于 2018-09-13T20:34:14.307 回答
3

试试这个

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    
    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

添加到viewDidAppear并且viewWillAppear当然会起作用。但viewDidAppear会给用户造成故障。

于 2018-09-20T10:29:16.903 回答
0

我们在项目中遇到了同样的问题。我们还注意到 iOS 12 中多个设备之间的差异,需要调用layoutIfNeeded& invalidateLayout。该解决方案基于@DHennessy13 的方法,但不需要布尔值来管理看起来有点笨拙的状态。

这里它基于一个 Rx 代码,基本上第一行是当数据发生变化时,里面subscribe是修复讨厌的 iOS 12 UI 故障需要做的事情:

        viewModel.cellModels.asObservable()
            .subscribe(onNext: { [weak self] _ in
                // iOS 12 bug in UICollectionView for cell size
                self?.collectionView.layoutIfNeeded()

                // required for iPhone 8 iOS 12 bug cell size
                self?.collectionView.collectionViewLayout.invalidateLayout()
            })
            .disposed(by: rx.disposeBag)

编辑:

顺便说一句,这似乎是 iOS 12 中的一个已知问题: https ://developer.apple.com/documentation/ios_release_notes/ios_12_release_notes (在 UIKit 部分)。

于 2018-09-18T15:08:13.290 回答
0
class CollectionViewCell: UICollectionViewCell {
@IBOutlet weak var label: UILabel!

override func awakeFromNib() {
    super.awakeFromNib()

    contentView.translatesAutoresizingMaskIntoConstraints = false

    let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor)
    let rightConstraint = contentView.rightAnchor.constraint(equalTo: rightAnchor)
    let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor)
    let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
    NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
}}

我已经尝试过了,它对我有用。试试这个,如果您需要任何帮助,请告诉我。

于 2021-12-11T11:24:47.237 回答