我试图得到自我大小UICollectionViewCells工作与自动布局,但我似乎不能让单元格大小自己的内容。我很难理解如何从单元格的contentView内的内容更新单元格的大小。

下面是我尝试过的设置:

自定义UICollectionViewCell,在它的contentView中有一个UITextView。 UITextView的滚动被禁用。 contentView的水平约束是:"H:|[_textView(320)]",也就是说UITextView被固定在单元格的左边,显式宽度为320。 contentView的垂直约束是:"V:|-0-[_textView]",也就是说UITextView固定在单元格的顶部。 UITextView有一个高度约束设置为常数,UITextView报告将适合文本。

下面是单元格背景设置为红色,UITextView背景设置为蓝色时的效果:

我把我一直在GitHub上玩的项目放在这里。


当前回答

这个答案在iOS 14中已经过时了,因为添加了复合布局。请考虑更新新的API

Swift 5更新

preferredLayoutAttributesFittingAttributes重命名为preferredLayoutAttributesFitting并使用自动调整大小


Swift 4更新

systemLayoutSizeFittingSize重命名为systemlayoutsizefiting


iOS 9更新

在看到我的GitHub解决方案在iOS 9下崩溃后,我终于有时间全面调查这个问题。我现在已经更新了repo,以包括几个不同配置的自调整单元格的示例。我的结论是,自我调整单元格在理论上很好,但在实践中很混乱。在处理自调整单元格时要注意一点。

博士TL;

看看我的GitHub项目


自我调整单元格只支持流布局,所以要确保这是你正在使用的。

要使自调整大小的单元格工作,需要设置两件事。

# 1。在UICollectionViewFlowLayout上设置estimatedItemSize

一旦您设置了estimatedItemSize属性,流布局将在本质上成为动态的。

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

# 2。在单元格子类上添加大小调整支持

这有两种口味;自动布局或自定义覆盖preferredLayoutAttributesFittingAttributes。

使用自动布局创建和配置单元格

我不会详细介绍这一点,因为有一篇关于为单元配置约束的精彩SO帖子。只是要小心,Xcode 6打破了一大堆东西与iOS 7,所以,如果你支持iOS 7,你将需要做的事情,如确保autoresizingMask设置在单元格的contentView和contentView的边界设置为单元格的边界时加载单元格(即awakeFromNib)。

你需要注意的是你的单元格需要比表格视图单元格更严格的约束。例如,如果你想要你的宽度是动态的,那么你的单元格需要一个高度约束。同样地,如果你想要高度是动态的,那么你需要一个宽度约束你的单元格。

在自定义单元格中实现preferredLayoutAttributesFittingAttributes

当这个函数被调用时,你的视图已经配置了内容(即cellForItem已经被调用)。假设你已经适当地设置了约束,你可以有一个这样的实现:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

NOTE On iOS 9 the behaviour changed a bit that could cause crashes on your implementation if you are not careful (See more here). When you implement preferredLayoutAttributesFittingAttributes you need to ensure that you only change the frame of your layout attributes once. If you don't do this the layout will call your implementation indefinitely and eventually crash. One solution is to cache the calculated size in your cell and invalidate this anytime you reuse the cell or change its content as I have done with the isHeightCalculated property.

体验你的布局

此时,您的collectionView中应该有“功能正常”的动态单元格。我还没有发现开箱即用的解决方案充分在我的测试,所以请随意评论,如果你有。在我看来,UITableView仍然赢得了动态大小的战斗。

##Caveats Be very mindful that if you are using prototype cells to calculate the estimatedItemSize - this will break if your XIB uses size classes. The reason for this is that when you load your cell from a XIB its size class will be configured with Undefined. This will only be broken on iOS 8 and up since on iOS 7 the size class will be loaded based on the device (iPad = Regular-Any, iPhone = Compact-Any). You can either set the estimatedItemSize without loading the XIB, or you can load the cell from the XIB, add it to the collectionView (this will set the traitCollection), perform the layout, and then remove it from the superview. Alternatively you could also make your cell override the traitCollection getter and return the appropriate traits. It's up to you.


其他回答

在iOS 10+中,这是一个非常简单的2步过程。

Ensure that all your cell contents are placed within a single UIView (or inside a descendant of UIView like UIStackView which simplifies autolayout a lot). Just like with dynamically resizing UITableViewCells, the whole view hierarchy needs to have constraints configured, from the outermost container to the innermost view. That includes constraints between the UICollectionViewCell and the immediate childview Instruct the flowlayout of your UICollectionView to size automatically yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

对于那些尝试一切都没有运气的人来说,这是唯一让它为我工作的东西。 对于单元格内的多行标签,尝试添加以下神奇的行:

label.preferredMaxLayoutWidth = 200

更多信息:点击这里

干杯!

编辑11/19/19:对于iOS 13,只使用UICollectionViewCompositionalLayout估计高度。不要浪费时间处理这个坏的API。

在与此斗争了一段时间后,我注意到,如果你不禁用滚动,调整大小对UITextViews不起作用:

let textView = UITextView()
textView.scrollEnabled = false

上面的示例方法不能编译。以下是更正后的版本(但未经测试是否有效)。

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}

我做了一个动态单元格高度的集合视图。这里是git中心回购。

并且,挖掘出为什么preferredLayoutAttributesFittingAttributes被调用不止一次。实际上,它至少会被调用3次。

控制台日志图:

第一preferredLayoutAttributesFittingAttributes:

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

layoutAttributes.frame.size.height的当前状态是57.5。

第二preferredLayoutAttributesFittingAttributes:

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

单元格帧高度变为534.5,正如我们预期的那样。但是集合视图高度仍然为0。

3日preferredLayoutAttributesFittingAttributes:

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

您可以看到集合视图的高度从0更改为477。

行为类似于handle scroll:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

一开始,我以为这个方法只调用一次。所以我编码如下:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

这条线:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

将导致系统调用无限循环和应用程序崩溃。

任何大小改变,它将验证所有单元格的preferredLayoutAttributesFittingAttributes一次又一次,直到每个单元格的位置(即帧)不再改变。