ios 使用自动布局在 UICollectionView 中指定单元格的一维
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26143591/
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
Specifying one Dimension of Cells in UICollectionView using Auto Layout
提问by Anthony Mattox
In iOS 8 the UICollectionViewFlowLayout
supports automatically resizing cells based on their own content size. This resizes the cells in both width and height according to their content.
在 iOS 8 中,UICollectionViewFlowLayout
支持根据自己的内容大小自动调整单元格的大小。这会根据内容调整单元格的宽度和高度。
Is it possible to specify a fixed value for the width (or height) of all the cells and allow the other dimensions to resize?
是否可以为所有单元格的宽度(或高度)指定一个固定值并允许其他尺寸调整大小?
For a simple example consider a multi-line label in a cell with constraints positioning it to the sides of the cell. The multi-line label could be resized different ways to accommodate the text. The cell should fill the width of the collection view and adjust it's height accordingly. Instead, the cells are sized haphazardly and it even causes a crash when the cell size is larger than the non-scrollable dimension of the collection view.
举一个简单的例子,考虑一个单元格中的多行标签,约束条件将它定位到单元格的两侧。多行标签可以以不同的方式调整大小以适应文本。单元格应该填充集合视图的宽度并相应地调整它的高度。相反,单元格的大小是随意的,当单元格大小大于集合视图的不可滚动尺寸时,它甚至会导致崩溃。
iOS 8 introduces the method systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority:
For each cell in the collection view the layout calls this method on the cell, passing in the estimated size. What would make sense to me would be to override this method on the cell, pass in the size that is given and set the horizontal constraint to required and a low priority to the vertical constraint. This way the horizontal size is fixed to the value set in the layout and the vertical size can be flexible.
iOS 8 引入了systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority:
对于集合视图中的每个单元格的方法,布局在单元格上调用此方法,传入估计的大小。对我来说有意义的是在单元格上覆盖此方法,传入给定的大小并将水平约束设置为 required 并将低优先级设置为垂直约束。这样水平尺寸固定为布局中设置的值,垂直尺寸可以灵活。
Something like this:
像这样的东西:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return attributes;
}
The sizes given back by this method, however, are completely strange. The documentation on this method is very unclear to me and mentions using the constants UILayoutFittingCompressedSize
UILayoutFittingExpandedSize
which just represent a zero size and a pretty large one.
然而,这种方法给出的大小是完全奇怪的。关于此方法的文档对我来说非常不清楚,并提到使用UILayoutFittingCompressedSize
UILayoutFittingExpandedSize
仅代表零大小和相当大大小的常量。
Is the size
parameter of this method really just a way to pass in two constants? Is there no way to achieve the behavior I expect of getting the appropriate height for a given size?
size
这个方法的参数真的只是传入两个常量的一种方式吗?有没有办法实现我期望为给定尺寸获得适当高度的行为?
Alternate Solutions
替代解决方案
1) Adding constraints that will be specify a specific width for the cell achieves the correct layout. This is a poor solution because that constraint should be set to the size of the cell's collection view which it has no safe reference to. The value for that constraint could be passed in when the cell is configured, but that also seems completely counterintuitive. This is also awkward because adding constraints directly to a cell or it's content view is causing many problems.
1) 添加将为单元格指定特定宽度的约束以实现正确的布局。这是一个糟糕的解决方案,因为该约束应设置为它没有安全引用的单元格集合视图的大小。可以在配置单元时传入该约束的值,但这似乎也完全违反直觉。这也很尴尬,因为直接向单元格或其内容视图添加约束会导致许多问题。
2) Use a table view. Table views work this way out of the box as cells have a fixed width, but this would not accommodate other situations like an iPad layout with fixed width cells in multiple columns.
2)使用表格视图。表格视图开箱即用,因为单元格具有固定宽度,但这不能适应其他情况,例如 iPad 布局在多列中具有固定宽度单元格。
回答by algal
It sounds like what you are asking for is a way to use UICollectionView to produce a layout like UITableView. If that's really what you want, the right way to do this is with a custom UICollectionViewLayout subclass (maybe something like SBTableLayout).
听起来您要求的是一种使用 UICollectionView 生成 UITableView 之类的布局的方法。如果这确实是您想要的,那么正确的方法是使用自定义 UICollectionViewLayout 子类(可能类似于SBTableLayout)。
On the other hand, if you're really asking if there is a clean way to do this with the default UICollectionViewFlowLayout, then I believe there is no way. Even with iOS8's self-sizing cells, it is not straightforward. The fundamental problem, as you say, is that the flow layout's machinery provides no way to fix one dimension and let another respond. (In addition, even if you could, there would be additional complexity around needing two layout passes to size the multi-line labels. This might not fit with how self-sizing cells want to compute all sizing via one call to systemLayoutSizeFittingSize.)
另一方面,如果你真的问是否有一种干净的方法可以使用默认的 UICollectionViewFlowLayout 来做到这一点,那么我相信没有办法。即使使用 iOS8 的自定大小单元格,也不是那么简单。正如您所说,基本问题是流布局的机制无法修复一个维度并让另一个维度做出响应。(此外,即使可以,需要两次布局传递来调整多行标签的大小也会带来额外的复杂性。这可能不适合自调整大小的单元格希望通过一次调用 systemLayoutSizeFittingSize 来计算所有大小的方式。)
However, if you still want to create a tableview-like layout with a flow layout, with cells that determine their own size, and respond naturally to the collection view's width, of course it is possible. There is still the messy way. I have done it with a "sizing cell", i.e., a non-displayed UICollectionViewCell that the controller keeps only for calculating cell sizes.
然而,如果你仍然想创建一个类似 tableview 的布局,使用流布局,单元格决定自己的大小,并自然地响应集合视图的宽度,当然这是可能的。还是有乱七八糟的方式。我已经使用“调整单元格大小”完成了它,即控制器保留仅用于计算单元格大小的非显示 UICollectionViewCell。
There are two parts to this approach. The first part is for the collection view delegate to calculate the correct cell size, by taking in the collection view's width and using the sizing cell to calculate the cell's height.
这种方法有两个部分。第一部分是让集合视图委托计算正确的单元格大小,通过获取集合视图的宽度并使用大小调整单元格来计算单元格的高度。
In your UICollectionViewDelegateFlowLayout, you implement a method like this:
在您的 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
}
This will cause the collection view to set the width of the cell based on the enclosing collection view, but then ask the sizing cell to calculate the height.
这将导致集合视图根据封闭的集合视图设置单元格的宽度,然后要求调整大小的单元格计算高度。
The second part is to get the sizing cell to use its own AL constraints to calculate the height. This can be harder than it should be, because of the way multi-line UILabel's effectively require a two-stage layout process. The work is done in the method preferredLayoutSizeFittingSize
, which is like so:
第二部分是让sizing cell使用自己的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
}
(This code is adapted from the Apple sample code from the sample code of the WWDC2014 session on "Advanced Collection View".)
(此代码改编自 Apple 示例代码,来自“高级收藏视图”上的 WWDC2014 会议的示例代码。)
A couple points to notice. It's using layoutIfNeeded() to force layout of the entire cell, in order to compute and set the width of the label. But that's not enough. I believe you also need to set preferredMaxLayoutWidth
so that the label will use that width with Auto Layout. And only then can you use systemLayoutSizeFittingSize
in order to get the cell to compute its height while taking the label into account.
有几点需要注意。它使用 layoutIfNeeded() 来强制整个单元格的布局,以便计算和设置标签的宽度。但这还不够。我相信您还需要设置preferredMaxLayoutWidth
以便标签将在自动布局中使用该宽度。只有这样,您systemLayoutSizeFittingSize
才能使用以使单元格在考虑标签的同时计算其高度。
Do I like this approach? No!! It feels way too complex, and it does layout twice. But as long as performance doesn't become an issue, I'd rather perform layout twice at runtime than have to define it twice in code, which seems to be the only other alternative.
我喜欢这种方法吗?不!!感觉太复杂了,而且布局了两次。但是只要性能不成为问题,我宁愿在运行时执行两次布局而不是必须在代码中定义它两次,这似乎是唯一的其他选择。
My hope is that eventually self-sizing cells will work differently and this will all get a lot simpler.
我希望最终自定大小的单元格会以不同的方式工作,这一切都会变得更简单。
Example projectshowing it at work.
显示它在工作中的示例项目。
But why not just use self-sizing cells?
但为什么不只使用自定大小的单元格呢?
In theory, iOS8's new facilities for "self-sizing cells" should make this unnecessary. If you've defined a cell with Auto Layout (AL), then the collection view should be smart enough to let it size itself and lay itself out correctly. In practice, I haven't seen any examples that have gotten this to work with multi-line labels. I think thisis partly because the self-sizing cell mechanism is still buggy.
从理论上讲,iOS8 的“自动调整单元格大小”的新功能应该使这成为不必要的。如果你已经定义了一个带有自动布局 (AL) 的单元格,那么集合视图应该足够智能,可以让它自行调整大小并正确布局。在实践中,我还没有看到任何可以将其与多行标签一起使用的示例。我认为这部分是因为自定大小的单元机制仍然有问题。
But I'd bet it's mostly because of the usual trickiness of Auto Layout and labels, which is that UILabels require a basically two-step layout process. It's not clear to me how you can perform both steps with self-sizing cells.
但我敢打赌,这主要是因为自动布局和标签通常很棘手,即 UILabels 需要一个基本的两步布局过程。我不清楚如何使用自动调整大小的单元来执行这两个步骤。
And like I said, this is really a job for a different layout. It is part of flow layout's essence that it positions things that have a size, rather than fixes a width and lets them choose their height.
就像我说的,这真的是一个不同布局的工作。流布局本质的一部分是它定位具有大小的事物,而不是固定宽度并让它们选择高度。
And what about preferredLayoutAttributesFittingAttributes: ?
那么 preferredLayoutAttributesFittingAttributes: 呢?
The preferredLayoutAttributesFittingAttributes:
method is a red herring, I think. That is only there to be used with the new self-sizing cell mechanism. So this isn't the answer as long as that mechanism is unreliable.
preferredLayoutAttributesFittingAttributes:
我认为,该方法是一个红鲱鱼。那只能与新的自定大小单元机制一起使用。因此,只要该机制不可靠,这就不是答案。
And what's up with systemlayoutSizeFittingSize:?
systemlayoutSizeFittingSize: 怎么了?
You're right the docs are confusing.
你是对的,文档令人困惑。
The docs on systemLayoutSizeFittingSize:
and systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
both suggest that you should only pass UILayoutFittingCompressedSize
and UILayoutFittingExpandedSize
as the targetSize
. However, the method signature itself, the header comments, and the behavior of the functions indicate that they are responding to the exact value of the targetSize
parameter.
上DocssystemLayoutSizeFittingSize:
和systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
两个建议,你应该只通过UILayoutFittingCompressedSize
与UILayoutFittingExpandedSize
作为targetSize
。但是,方法签名本身、标题注释和函数的行为表明它们正在响应targetSize
参数的确切值。
In fact, if you set the UICollectionViewFlowLayoutDelegate.estimatedItemSize
, in order to enable the new self-sizing cell mechanism, that value seems to get passed in as the targetSize. And UILabel.systemLayoutSizeFittingSize
seems to return the exact same values as UILabel.sizeThatFits
. This is suspicious, given that the argument to systemLayoutSizeFittingSize
is supposed to be a rough target and the argument to sizeThatFits:
is supposed to be a maximum circumscribing size.
事实上,如果您设置UICollectionViewFlowLayoutDelegate.estimatedItemSize
, 以启用新的自调整单元格机制,该值似乎会作为 targetSize 传入。并且UILabel.systemLayoutSizeFittingSize
似乎返回与UILabel.sizeThatFits
. 这是可疑的,因为参数 tosystemLayoutSizeFittingSize
应该是一个粗略的目标,而参数 tosizeThatFits:
应该是最大的限制大小。
More Resources
更多资源
While it is sad to think that such a routine requirement should require "research resources", I think it does. Good examples and discussions are:
虽然认为这样的常规要求需要“研究资源”令人遗憾,但我认为确实如此。很好的例子和讨论是:
- 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
- code for WWDC2014 session 232, "Advanced User Interfaces with Collection Views"
- 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
- WWDC2014 session 232 的代码,“具有集合视图的高级用户界面”
回答by Jordan Smith
There's a cleaner way to do this than some of the other answers here, and it works well. It should be performant (collection views load fast, no unnecessary auto layout passes etc), and doesn't have any 'magic numbers' like a fixed collection view width. Changing the collection view size, e.g. on rotation, and then invalidating the layout should work great too.
与这里的其他一些答案相比,有一种更简洁的方法可以做到这一点,并且效果很好。它应该是高性能的(集合视图加载速度快,没有不必要的自动布局传递等),并且没有任何像固定集合视图宽度这样的“神奇数字”。更改集合视图大小,例如在旋转时,然后使布局无效也应该很好用。
1. Create the following flow layout subclass
1.创建如下流布局子类
class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {
// Don't forget to use this class in your storyboard (or code, .xib etc)
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
guard let collectionView = collectionView else { return attributes }
attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
return attributes
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let allAttributes = super.layoutAttributesForElementsInRect(rect)
return allAttributes?.flatMap { attributes in
switch attributes.representedElementCategory {
case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
default: return attributes
}
}
}
}
2. Register your collection view for automatic sizing
2. 注册您的集合视图以自动调整大小
// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)
flowLayout.estimatedItemSize = flowLayout.itemSize
3. Use the predefined width + custom height in your cell subclass
3.在你的单元格子类中使用预定义的宽度+自定义高度
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return layoutAttributes
}
回答by Tanguy G.
A simple way to do it in iOS 9 in a few lines of codes - the horizontal way exemple (fixing its height to its Collection View height) :
在 iOS 9 中使用几行代码的简单方法 - 水平方式示例(将其高度固定为其集合视图高度):
Init your Collection View Flow Layout with an estimatedItemSize
to enable self-sizing cell :
使用 初始化您的 Collection View Flow LayoutestimatedItemSize
以启用 self-sizing cell :
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);
Implement the Collection View Layout Delegate (in your View Controller most of the time), collectionView:layout:sizeForItemAtIndexPath:
. The goal here is to set the fixed height (or width) to the Collection View dimension. The 10 value can be anything, but you should set it to a value that doesn't break constraints :
实现集合视图布局代表(在视图控制器的大部分时间)collectionView:layout:sizeForItemAtIndexPath:
。这里的目标是将固定高度(或宽度)设置为集合视图维度。10 值可以是任何值,但您应该将其设置为不破坏约束的值:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}
Override your custom cell preferredLayoutAttributesFittingAttributes:
method, this part actually calculate your dynamic cell width based on your Auto Layout constraints and the height you have just set :
覆盖您的自定义单元格preferredLayoutAttributesFittingAttributes:
方法,这部分实际上根据您的自动布局约束和您刚刚设置的高度计算您的动态单元格宽度:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
CGRect frame = attributes.frame;
frame.size.width = desiredWidth;
attributes.frame = frame;
return attributes;
}
回答by Daniel Galasko
Try fixing your width in the preferred layout attributes:
尝试在首选布局属性中修复您的宽度:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGRect newFrame = attr.frame;
newFrame.size.height = size.height;
attr.frame = newFrame;
return attr;
}
Naturally you also want to ensure that you setup your layout correctly to:
当然,您还希望确保正确设置布局以:
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];
Heres something I put on Githubthat uses constant width cells and supports dynamic type so the height of the cells updates as the system font size changes.
这是我放在Github上的一些东西,它使用恒定宽度的单元格并支持动态类型,因此单元格的高度会随着系统字体大小的变化而更新。
回答by Dhaivat Vyas
YES it can be done using auto layout programmatically and by setting constraints in storyboard or xib. You need to add constraint for width size to remain constant and set height greater than or equal to.
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
Hope this will be helpful and solve your issue.
是的,它可以通过编程方式使用自动布局并通过在故事板或 xib 中设置约束来完成。您需要为宽度大小添加约束以保持恒定并将高度设置为大于或等于。
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
希望这会是有帮助并解决您的问题。