具有自动布局的 UICollectionView 自动调整单元格大小 [英] UICollectionView Self Sizing Cells with Auto Layout

查看:50
本文介绍了具有自动布局的 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.

推荐答案

这个答案在 iOS 14 中已经过时,添加了组合布局.请考虑更新新API

为 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 = 常规-任意,iPhone = 紧凑-任意).您可以设置estimatedItemSize而不加载XIB,或者您可以从XIB加载单元格,将其添加到collectionView(这将设置traitCollection),执行布局,然后将其从superview中删除.或者,您也可以让您的单元格覆盖 traitCollection 获取器并返回适当的特征.这取决于你.

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

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

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

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

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆