本文介绍了UICollectionView 具有自动布局的自定尺寸单元格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to get self sizing UICollectionViewCells working 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.

这是我尝试过的设置:

  • 自定义 UICollectionViewCell 在其 contentView 中带有 UITextView.
  • UITextView 的滚动功能已禁用.
  • contentView 的水平约束为:H:|[_textView(320)]",即 UITextView 固定在单元格的左侧,显式宽度为 320.
  • contentView 的垂直约束为:V:|-0-[_textView]",即 UITextView 固定在单元格顶部.
  • UITextView 有一个高度约束设置为一个常量,UITextView 报告将适合文本.
  • Custom UICollectionViewCell with a UITextView in its contentView.
  • Scrolling for the UITextView is disabled.
  • The contentView's horizontal constraint is: "H:|[_textView(320)]", i.e. the UITextView is 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 UITextView pinned to the top of the cell.
  • The UITextView has a height constraint set to a constant which the UITextView reports will fit the text.

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

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

我把我一直在玩的项目放在了 GitHub这里.

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

推荐答案

为 Swift 5 更新

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

Updated for Swift 5

preferredLayoutAttributesFittingAttributes renamed to preferredLayoutAttributesFitting and use auto sizing

systemLayoutSizeFittingSize 重命名为 systemLayoutSizeFitting

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

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.

TL;DR

查看我的 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.在 UICollectionViewFlowLayout

一旦您设置了 estimatedItemSize 属性,流布局本质上就会变成动态的.

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

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

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

#2. Add support for sizing on your cell subclass

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

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

我不会详细介绍这个,因为有一个很棒的 SO post 关于为单元格配置约束.请注意 Xcode 6 破坏了一堆东西iOS 7 因此,如果您支持 iOS 7,您将需要确保在单元格的 contentView 上设置 autoresizingMask,并且在加载单元格时将 contentView 的边界设置为单元格的边界(即 awakeFromNib).

I won't go to in to detail about this as there's a brilliant SO post about configuring constraints for a cell. Just be wary that Xcode 6 broke a 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).

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

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.

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

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

//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
}

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

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 赢得了动态大小的战斗恕我直言.

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 UITableView wins the battle for dynamic sizing IMHO.

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

##CaveatsBe 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.

如果我遗漏了什么,请告诉我,希望我能帮上忙,祝你编程好运

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

这篇关于UICollectionView 具有自动布局的自定尺寸单元格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 15:08