在没有 `invalidateLayout` 跳转的情况下自动调整 UICollectionViewLayout 中的单元格(支持 iOS 9)

Autosizing cells in UICollectionViewLayout (supporting iOS 9) without `invalidateLayout` jump

我一直在阅读 this article about using custom UICollectionViewLayouts 并尝试将这个想法融入我正在从事的项目中。

我们之前使用的 UICollectionViewFlowLayout 子类有一个巨大的丑陋函数来确定单元格的大小,方法是将原型单元格出列并填充它,然后询问它的大小。

不用说,我想找到更好的方法。

我遇到的问题是单元格的初始布局。我们使用的许多单元格包含 1 或 2 个标签,这些标签可能包含大量文本并且需要换行。在链接的文章中有一个游乐场,它显示自动调整大小的多行标签,但它在文本中插入换行符以强制执行此操作...

在 Datasource.swift 文件中...

lazy private var values: [String] = {
    return (0...255).map {
        _ in
        switch arc4random_uniform(3) {
        case 0: return "Hello"
        case 1: return "Hello\nGoodbye"
        default: return "Hello\nGoodbye\nAu revoir"
        }
    }
}()

但是,如果我将这些更改为自然会中断的不同长度的字符串,则会破坏示例。初始布局在标签中只有一行。但是,在滚动标签时正确显示。

我可以 "fix" 通过在 Autosizing 文件中添加行 layout.invalidateLayout() 来做到这一点。

现在,我在自己的项目中尝试做同样的事情。如果我将 layout.invalidateLayout() 放在 viewDidAppear 函数中,则只能在视图控制器中使用它。这意味着在转换布局不正确然后 "jumps" 到正确布局之后,我们会得到一个笨拙的跳转。但是,我觉得使用 invalidateLayout 的整个方法都被破坏了。

有什么方法可以让它不破坏初始布局?或者 intelligently 使单元格初始布局无效?

我添加了一个 GitHub project 来说明我遇到的问题。由于代码,目前它的布局正确...

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

    // This invalidation "fixes" the layout but causes a jump in the UI
    collectionView?.collectionViewLayout.invalidateLayout()
}

理想情况下,我想删除它并拥有布局 "just work"。

这里的问题是,在初始传递时,单元格返回的首选布局属性不正确 - 它根据看似无限的宽度调整标签大小,因此您最终想要将其放在一行中。

将此行添加到单元格 setUp() 方法 "fixes" 问题:

label.preferredMaxLayoutWidth = self.bounds.width - (contentView.layoutMargins.left + contentView.layoutMargins.right)

有点脏,我相信有更好的方法。

编辑

好的,这是一个更好的方法。这会迫使您的视图进入布局中的原始估计框架,从而使 AL 引擎有机会正确调整单元格的大小。在您的单元格子类中执行此操作:

private var isInitialLayout = true

public override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    if isInitialLayout {
        self.contentView.layoutIfNeeded()
        isInitialLayout = false
    }
    return super.preferredLayoutAttributesFitting(layoutAttributes)
}