听起来您要求的是一种使用 UICollectionView 生成类似 UITableView 的布局的方法。如果这确实是您想要的,那么正确的方法是使用自定义 UICollectionViewLayout 子类(可能类似于SBTableLayout https://github.com/blommegard/SBTableLayout).
另一方面,如果您真的想知道是否有一种干净的方法可以使用默认的 UICollectionViewFlowLayout 来执行此操作,那么我相信没有办法。即使 iOS8 具有自动调整单元大小的功能,这也不是一件简单的事。正如您所说,根本问题是流布局的机制无法提供固定一个维度并让另一个维度做出响应的方法。 (此外,即使可以,需要两次布局传递来调整多行标签的大小也会带来额外的复杂性。这可能不符合自调整单元格希望通过一次调用 systemLayoutSizeFittingSize 来计算所有大小的方式。)
但是,如果您仍然想创建一个具有流布局的类似 tableview 的布局,其中单元格可以确定自己的大小,并自然地响应集合视图的宽度,当然这是可能的。还是有那种乱七八糟的方式。我已经使用“调整单元格”来完成此操作,即控制器仅保留用于计算单元格大小的未显示的 UICollectionViewCell。
这种方法有两个部分。第一部分是让集合视图委托通过获取集合视图的宽度并使用调整大小的单元格来计算单元格的高度来计算正确的单元格大小。
在 UICollectionViewDelegateFlowLayout 中,您实现如下方法:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
这将导致集合视图根据封闭的集合视图设置单元格的宽度,然后要求调整大小的单元格计算高度。
第二部分是让尺寸调整单元使用自己的 AL 约束来计算高度。这可能比应有的更难,因为多行 UILabel 实际上需要两阶段布局过程。工作是在方法中完成的preferredLayoutSizeFittingSize
,就像这样:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(此代码改编自 WWDC2014 会议上“高级集合视图”的示例代码中的 Apple 示例代码。)
有几点需要注意。它使用layoutIfNeeded()强制整个单元格的布局,以便计算和设置标签的宽度。但这还不够。我相信你还需要设置preferredMaxLayoutWidth
这样标签将在自动布局中使用该宽度。只有这样你才能使用systemLayoutSizeFittingSize
为了让单元格在考虑标签的同时计算其高度。
我喜欢这种方法吗?不!!感觉太复杂了,而且布局了两次。但只要性能不成为问题,我宁愿在运行时执行两次布局,也不愿在代码中定义两次,这似乎是唯一的其他选择。
我希望最终自调整大小的单元能够以不同的方式工作,这一切都会变得更加简单。
示例项目 https://github.com/algal/UICVVariableHeightiOS8在工作中展示它。
但为什么不直接使用自调整大小的单元格呢?
理论上,iOS8 的“自动调整单元大小”的新功能应该使这种情况变得不必要。如果您使用自动布局(AL)定义了一个单元格,那么集合视图应该足够智能,可以让它自行调整大小并正确布局。在实践中,我还没有看到任何可以使用多行标签的示例。我认为this https://github.com/algal/SelfSizingCellsDemo部分原因是自调整单元大小的机制仍然存在缺陷。
但我敢打赌这主要是因为自动布局和标签通常很棘手,即 UILabels 需要基本上两步的布局过程。我不清楚如何使用自动调整大小的单元格执行这两个步骤。
正如我所说,这确实是一项不同布局的工作。它是流布局本质的一部分,它定位具有大小的事物,而不是固定宽度并让它们选择它们的高度。
那么 PreferredLayoutAttributesFitting Attributes: 又如何呢?
The preferredLayoutAttributesFittingAttributes:
我认为,这种方法是一种转移注意力的方法。这只能与新的自调整大小单元机制一起使用。因此,只要该机制不可靠,这就不是答案。
systemlayoutSizeFittingSize: 是怎么回事?
你是对的,文档令人困惑。
文档上systemLayoutSizeFittingSize:
and systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
两者都建议你应该只通过UILayoutFittingCompressedSize
and UILayoutFittingExpandedSize
as the targetSize
。但是,方法签名本身、标头注释和函数的行为表明它们正在响应targetSize
范围。
事实上,如果你设置UICollectionViewFlowLayoutDelegate.estimatedItemSize
,为了启用新的自调整单元格大小机制,该值似乎作为 targetSize 传入。和UILabel.systemLayoutSizeFittingSize
似乎返回与完全相同的值UILabel.sizeThatFits
。这是可疑的,因为这个论点systemLayoutSizeFittingSize
应该是一个粗略的目标和论据sizeThatFits:
应该是最大外接尺寸。
更多资源
虽然令人遗憾的是,这样的例行要求竟然需要“研究资源”,但我认为确实如此。很好的例子和讨论是:
- http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
- http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html
- WWDC2014 会议 232 的代码,“具有集合视图的高级用户界面”