ios 具有自动布局的 UICollectionView 自动调整单元格大小

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25895311/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:32:37  来源:igfitidea点击:

UICollectionView Self Sizing Cells with Auto Layout

iosuicollectionviewautolayoutuicollectionviewcell

提问by rawbee

I'm trying to get self sizing UICollectionViewCellsworking with Auto Layout, but I can't seem to get the cells to size themselves to the content. I'm having trouble understanding how the cell's size is updated from the contents of what's inside the cell's contentView.

我正在尝试UICollectionViewCells使用自动布局进行自我调整大小,但我似乎无法让单元格根据内容调整自己的大小。我无法理解单元格的大小是如何从单元格的 contentView 中的内容更新的。

Here's the setup I've tried:

这是我尝试过的设置:

  • Custom UICollectionViewCellwith a UITextViewin its contentView.
  • Scrolling for the UITextViewis disabled.
  • The contentView's horizontal constraint is: "H:|[_textView(320)]", i.e. the UITextViewis pinned to the left of the cell with an explicit width of 320.
  • The contentView's vertical constraint is: "V:|-0-[_textView]", i.e. the UITextViewpinned to the top of the cell.
  • The UITextViewhas a height constraint set to a constant which the UITextViewreports will fit the text.
  • 自定义UICollectionViewCellUITextView在其内容查看。
  • 滚动UITextView被禁用。
  • contentView 的水平约束为:“H:|[_textView(320)]”,即UITextView固定在单元格左侧,显式宽度为 320。
  • contentView 的垂直约束是:“V:|-0-[_textView]”,即UITextView固定在单元格的顶部。
  • UITextView具有高度限制设置为其中一个恒定的UITextView报告将适合文本。

Here's what it looks like with the cell background set to red, and the UITextViewbackground set to Blue: cell background red, UITextView background blue

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

I put the project that I've been playing with on GitHub here.

我把我已经在GitHub上玩的项目在这里

回答by Daniel Galasko

Updated for Swift 5

为 Swift 5 更新

preferredLayoutAttributesFittingAttributesrenamed to preferredLayoutAttributesFittingand use auto sizing

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



Updated for Swift 4

为 Swift 4 更新

systemLayoutSizeFittingSizerenamed to systemLayoutSizeFitting

systemLayoutSizeFittingSize重命名为 systemLayoutSizeFitting



Updated for iOS 9

针对 iOS 9 更新

After seeing my GitHub solution break under iOS 9 I finally got the time to investigate the issue fully. I have now updated the repo to include several examples of different configurations for self sizing cells. My conclusion is that self sizing cells are great in theory but messy in practice. A word of caution when proceeding with self sizing cells.

在看到我的 GitHub 解决方案在 iOS 9 下崩溃后,我终于有时间全面调查这个问题。我现在已经更新了 repo 以包含几个不同配置的示例,用于自定大小的单元格。我的结论是,自定尺寸单元在理论上很棒,但在实践中却很混乱。使用自定大小单元格时要小心。

TL;DR

TL; 博士

Check out my GitHub project

查看我的GitHub 项目



Self sizing cells are only supported with flow layout so make sure thats what you are using.

自定义大小单元格仅支持流布局,因此请确保这就是您正在使用的。

There are two things you need to setup for self sizing cells to work.

您需要设置两件事才能使自定大小单元格工作。

1. Set estimatedItemSizeon UICollectionViewFlowLayout

1.设置estimatedItemSizeUICollectionViewFlowLayout

Flow layout will become dynamic in nature once you set the estimatedItemSizeproperty.

设置estimatedItemSize属性后,流布局本质上将变得动态。

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Add support for sizing on your cell subclass

2. 添加对单元子类大小调整的支持

This comes in 2 flavours; Auto-Layout orcustom override of preferredLayoutAttributesFittingAttributes.

这有 2 种口味;的自动布局自定义覆盖preferredLayoutAttributesFittingAttributes

Create and configure cells with Auto Layout

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

I won't go to in to detail about this as there's a brilliant SO postabout configuring constraints for a cell. Just be wary that Xcode 6 brokea bunch of stuff with iOS 7 so, if you support iOS 7, you will need to do stuff like ensure the autoresizingMask is set on the cell's contentView and that the contentView's bounds is set as the cell's bounds when the cell is loaded (i.e. awakeFromNib).

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

Things you do need to be aware of is that your cell needs to be more seriously constrained than a Table View Cell. For instance, if you want your width to be dynamic then your cell needs a height constraint. Likewise, if you want the height to be dynamic then you will need a width constraint to your cell.

您需要注意的是,您的单元格需要比表视图单元格受到更严格的约束。例如,如果您希望您的宽度是动态的,那么您的单元格需要一个高度约束。同样,如果您希望高度是动态的,那么您将需要对单元格进行宽度约束。

Implement preferredLayoutAttributesFittingAttributesin your custom cell

preferredLayoutAttributesFittingAttributes在您的自定义单元格中实施

When this function is called your view has already been configured with content (i.e. cellForItemhas been called). Assuming your constraints have been appropriately set you could have an implementation like this:

当这个函数被调用时,你的视图已经配置了内容(即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
}

NOTEOn 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 preferredLayoutAttributesFittingAttributesyou 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 isHeightCalculatedproperty.

注意在 iOS 9 上,如果您不小心,行为会发生一些变化,这可能会导致您的实现崩溃(在此处查看更多信息)。实现时,preferredLayoutAttributesFittingAttributes您需要确保只更改一次布局属性的框架。如果您不这样做,布局将无限期地调用您的实现并最终崩溃。一种解决方案是将计算出的大小缓存在您的单元格中,并在您重用单元格或更改其内容时使其无效,就像我对isHeightCalculated属性所做的那样。

Experience your layout

体验您的布局

At this point you should have 'functioning' dynamic cells in your collectionView. I haven't yet found the out-of-the box solution sufficient during my tests so feel free to comment if you have. It still feels like UITableViewwins the battle for dynamic sizing IMHO.

此时,您的 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 traitCollectiongetter and return the appropriate traits. It's up to you.

请注意,如果您使用原型单元格来计算estimatedItemSize - 如果您的XIB 使用大小类,这将中断。这样做的原因是,当您从 XIB 加载单元格时,其大小类将配置为Undefined. 这只会在 iOS 8 及更高版本上被打破,因为在 iOS 7 上,大小类将根据设备加载(iPad = 常规-任意,iPhone = 紧凑-任意)。您可以设置estimatedItemSize而不加载XIB,或者您可以从XIB加载单元格,将其添加到collectionView(这将设置traitCollection),执行布局,然后将其从superview中删除。或者,您也可以让您的单元格覆盖traitCollectiongetter 并返回适当的特征。由你决定。

Let me know if I missed anything, hope I helped and good luck coding

如果我错过了什么,请告诉我,希望我有所帮助,祝您编码愉快



回答by pawel221

In iOS10 there is new constant called UICollectionViewFlowLayout.automaticSize(formerly UICollectionViewFlowLayoutAutomaticSize), so instead:

在 iOS10 中有一个名为UICollectionViewFlowLayout.automaticSize(以前称为UICollectionViewFlowLayoutAutomaticSize)的新常量,因此:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

you can use this:

你可以使用这个:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

It has better performance especially when cells in you collection view has constant wid

它具有更好的性能,尤其是当您集合视图中的单元格具有恒定宽度时

Accessing Flow Layout:

访问流布局:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Swift 5 Updated:

斯威夫特 5 更新:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}

回答by Matt Koala

A few key changes to Daniel Galasko's answerfixed all my problems. Unfortunately, I don't have enough reputation to comment directly (yet).

Daniel Galasko 的答案的一些关键更改解决了我的所有问题。不幸的是,我没有足够的声誉来直接评论(还)。

In step 1, when using Auto Layout, simply add a single parent UIView to the cell. EVERYTHING inside the cell must be a subview of the parent. That answered all of my problems. While Xcode adds this for UITableViewCells automatically, it doesn't (but it should) for UICollectionViewCells. According to the docs:

在第 1 步中,使用自动布局时,只需向单元格添加一个父级 UIView。单元格内的所有内容都必须是父级的子视图。那解决了我所有的问题。虽然 Xcode 会自动为 UITableViewCells 添加它,但它不会(但应该)为 UICollectionViewCells 添加。根据文档

To configure the appearance of your cell, add the views needed to present the data item's content as subviews to the view in the contentView property. Do not directly add subviews to the cell itself.

要配置单元格的外观,请将数据项的内容作为子视图呈现所需的视图添加到 contentView 属性中的视图。不要直接向单元格本身添加子视图。

Then skip step 3 entirely. It isn't needed.

然后完全跳过步骤 3。它不需要。

回答by Marmoy

In iOS 10+ this is a very simple 2 step process.

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

  1. 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

  2. Instruct the flowlayout of your UICollectionView to size automatically

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    
  1. 确保所有单元格内容都放置在单个 UIView 中(或放置在 UIView 的后代中,例如 UIStackView 这大大简化了自动布局)。就像动态调整 UITableViewCells 一样,整个视图层次结构都需要配置约束,从最外层的容器到最内层的视图。这包括 UICollectionViewCell 和直接子视图之间的约束

  2. 指示您的 UICollectionView 的 flowlayout 自动调整大小

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    

回答by dianakarenms

  1. Add flowLayout on viewDidLoad()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
    
  2. Also, set an UIView as mainContainer for your cell and add all required views inside it.

  3. Refer to this awesome, mind-blowing tutorial for further reference: UICollectionView with autosizing cell using autolayout in iOS 9 & 10

  1. 在 viewDidLoad() 上添加 flowLayout

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
    
  2. 此外,将 UIView 设置为您的单元格的 mainContainer 并在其中添加所有必需的视图。

  3. 请参阅这个很棒的、令人兴奋的教程以获取进一步参考: UICollectionView with autosizing cell using autolayout in iOS 9 & 10

回答by noobular

EDIT 11/19/19: For iOS 13, just use UICollectionViewCompositionalLayout with estimated heights. Don't waste your time dealing with this broken API.

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

After struggling with this for some time, I noticed that resizing does not work for UITextViews if you don't disable scrolling:

在为此苦苦挣扎了一段时间后,我注意到如果不禁用滚动,则调整大小对 UITextViews 不起作用:

let textView = UITextView()
textView.scrollEnabled = false

回答by Fattie

contentView anchor mystery:

contentView 主播之谜:

In one bizarre case this

在一个奇怪的案例中

    contentView.translatesAutoresizingMaskIntoConstraints = false

would not work. Added four explicit anchors to the contentView and it worked.

不会工作。向 contentView 添加了四个显式锚点并且它起作用了。

class AnnoyingCell: UICollectionViewCell {

    @IBOutlet var word: UILabel!

    override init(frame: CGRect) {
        super.init(frame: frame); common() }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }

    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

and as usual

和往常一样

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

in YourLayout: UICollectionViewFlowLayout

YourLayout: UICollectionViewFlowLayout

Who knows? Might help someone.

谁知道?可能会帮助某人。

Credit

信用

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

stumbled on to the tip there - never saw it anywhere else in all the 1000s articles on this.

偶然发现了那里的提示 - 在有关此的所有 1000 篇文章中从未在其他任何地方看到过。

回答by Yang Young

I did a dynamic cell height of collection view. Here is git hub repo.

我做了一个集合视图的动态单元格高度。这是git hub repo

And, dig out why preferredLayoutAttributesFittingAttributes is called more than once. Actually, it will be called at least 3 times.

并且,找出为什么会多次调用 preferredLayoutAttributesFittingAttributes。实际上,它至少会被调用 3 次。

The console log picture : enter image description here

控制台日志图片: 在此处输入图片说明

1st preferredLayoutAttributesFittingAttributes:

1st 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);

The layoutAttributes.frame.size.height is current status 57.5.

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

2nd preferredLayoutAttributesFittingAttributes:

第二个 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);

The cell frame height changed to 534.5as our expected. But, the collection view still zero height.

正如我们预期的那样,单元格框架高度更改为534.5。但是,集合视图仍然为零高度。

3rd preferredLayoutAttributesFittingAttributes:

第三个 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);

You can see the collection view height was changed from 0 to 477.

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

The behavior is similar to handle scroll:

行为类似于处理滚动:

1. Before self-sizing cell

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

3. Did changed self-sizing cell

At beginning, I thought this method only call once. So I coded as the following:

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

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

This line:

这一行:

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

will cause system call infinite loop and App crash.

会导致系统调用无限循环和App崩溃。

Any size changed, it will validate all cells' preferredLayoutAttributesFittingAttributes again and again until every cells' positions (i.e frames) are no more change.

任何大小改变,它都会一次又一次地验证所有单元格的 preferredLayoutAttributesFittingAttributes,直到每个单元格的位置(即框架)不再发生变化。

回答by Murat Yasar

In addition to above answers,

除了以上回答,

Just make sure you set estimatedItemSizeproperty of UICollectionViewFlowLayoutto some size and do notimplement sizeForItem:atIndexPathdelegate method.

只要确保你设置estimatedItemSize财产UICollectionViewFlowLayout一些大小和执行sizeForItem:atIndexPath委托方法。

That's it.

就是这样。

回答by Yang Young

If you implement UICollectionViewDelegateFlowLayout method:

如果你实现 UICollectionViewDelegateFlowLayout 方法:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

When you call collectionview performBatchUpdates:completion:, the size height will use sizeForItemAtIndexPathinstead of preferredLayoutAttributesFittingAttributes.

当您调用 时collectionview performBatchUpdates:completion:,尺寸高度将使用sizeForItemAtIndexPath而不是 preferredLayoutAttributesFittingAttributes

The rendering process of performBatchUpdates:completionwill go through the method preferredLayoutAttributesFittingAttributesbut it ignores your changes.

的渲染过程performBatchUpdates:completion将通过该方法,preferredLayoutAttributesFittingAttributes但它会忽略您的更改。