在iOS 12中,何时使用UICollectionView布局单元格,在nib中使用autolayout

Evi*_*dra 44 ios uicollectionview ios-autolayout ios12 xcode10

像这样的代码

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()
Run Code Online (Sandbox Code Playgroud)

细胞限制:

在此输入图像描述

当我在iOS 12上运行它时,它是不同的.左模拟器是iOS 11,右边是iOS 12:

在此输入图像描述

但是,当我滚动它时,细胞的帧将是正常的.


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

Cœu*_*œur 41

对于所有的解决方案,请注意,没有必要显式调用reloadDataviewDidLoad:它会自动发生.

解决方案1

灵感来自萨曼莎的想法:invalidateLayout异步进入viewDidLoad.

override func viewDidLoad() {
    super.viewDidLoad()

    //[...]

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

    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方案2

(不完美,见DHennessy13对它的改进)

根据Peter Lapisu的回答.invalidateLayoutviewWillLayoutSubviews.

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    collectionView.collectionViewLayout.invalidateLayout()
}
Run Code Online (Sandbox Code Playgroud)

正如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()
    }
}
Run Code Online (Sandbox Code Playgroud)

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

解决方案4

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

解决方案5

实施collectionView(_:layout:sizeForItemAt:)并返回cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).见哑光答案.

  • 解决方案2对我有用。谢谢 (2认同)

Lar*_*rry 28

这是另一个解决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])
    }
}
Run Code Online (Sandbox Code Playgroud)

这是通过回答启发ale84UICollectionViewFlowLayout estimatedItemSize并不虽然它正常工作与iOS 11. iOS12正常工作*


mat*_*att 23

问题是这里假设的特征 - 基于其内部约束自行调整大小的集合视图单元 - 不存在.它从未存在过.Apple声称确实如此,但事实并非如此.自收集意见出台以来,我每年都对此提出了一个错误.我的错误报告从未被关闭,因为错误是真实的.没有自我调整集合视图单元格这样的东西.

另见我的回答:https://stackoverflow.com/a/51585910/341994 \

在某些年份,尝试使用自我大小的细胞已经崩溃.在其他年份,它不会崩溃,但它会使布局错误.但它不起作用.

只有做这样的事情的方式是实现委托方法sizeForItemAt并提供大小自己.您可以通过致电轻松完成

cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
Run Code Online (Sandbox Code Playgroud)

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

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

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
}
Run Code Online (Sandbox Code Playgroud)


Sam*_*tha 9

我有同样的问题,单元格使用估计的大小(而不是自动大小),直到滚动.使用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()
}
Run Code Online (Sandbox Code Playgroud)


小智 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()
    }
}
Run Code Online (Sandbox Code Playgroud)