ios - 使用自动布局在 UICollectionView 中指定单元格的一维

在 iOS 8 中 UICollectionViewFlowLayout支持根据自己的内容大小自动调整单元格的大小。这会根据内容调整单元格的宽度和高度。

是否可以为所有单元格的宽度(或高度)指定一个固定值并允许其他尺寸调整大小?

举一个简单的例子,考虑一个单元格中的多行标签,其约束将其定位到单元格的两侧。多行标签可以以不同的方式调整大小以适应文本。单元格应该填充集合 View 的宽度并相应地调整它的高度。相反,单元格的大小是随意的,当单元格大小大于集合 View 的不可滚动尺寸时,它甚至会导致崩溃。

iOS 8 引入方法 systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority:对于集合 View 中的每个单元格,布局在单元格上调用此方法,传入估计的大小。对我来说有意义的是在单元格上覆盖此方法,传入给定的大小并将水平约束设置为 required 并将低优先级设置为垂直约束。这样水平尺寸固定为布局中设置的值,垂直尺寸可以灵活。

像这样的东西:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
    attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    return attributes;
}

然而,这种方法返回的大小是完全奇怪的。关于此方法的文档对我来说非常不清楚,并提到使用常量 UILayoutFittingCompressedSize UILayoutFittingExpandedSize它只代表零尺寸和相当大的尺寸。

size这个方法的参数真的只是传入两个常量的一种方式吗?有没有办法实现我期望为给定尺寸获得适当高度的行为?

替代解决方案

1) 添加将为单元格指定特定宽度的约束以实现正确的布局。这是一个糟糕的解决方案,因为该约束应设置为它没有安全引用的单元格集合 View 的大小。可以在配置单元时传入该约束的值,但这似乎也完全违反直觉。这也很尴尬,因为直接向单元格或其内容 View 添加约束会导致许多问题。

2)使用表格 View 。表格 View 开箱即用,因为单元格具有固定宽度,但这不能适应其他情况,例如 iPad 布局在多列中具有固定宽度单元格。

最佳答案

听起来您要求的是一种使用 UICollectionView 生成 UITableView 之类的布局的方法。如果这确实是您想要的,那么正确的方法是使用自定义 UICollectionViewLayout 子类(可能类似于 SBTableLayout )。

另一方面,如果你真的问是否有一种干净的方法可以使用默认的 UICollectionViewFlowLayout 来做到这一点,那么我相信没有办法。即使使用 iOS8 的自定大小单元格,也不是那么简单。正如您所说,基本问题是流布局的机制无法修复一个维度并让另一个维度做出响应。 (此外,即使可以,需要两次布局传递来调整多行标签的大小也会带来额外的复杂性。这可能不符合自调整大小单元格希望通过一次调用 systemLayoutSizeFittingSize 来计算所有大小的方式。)

然而,如果你仍然想创建一个类似 tableview 的布局,使用流布局,单元格决定自己的大小,并自然地响应集合 View 的宽度,当然这是可能的。还是有乱七八糟的方式。我已经使用“调整单元格大小”完成了它,即 Controller 保留仅用于计算单元格大小的非显示 UICollectionViewCell。

这种方法有两个部分。第一部分是让集合 View 委托(delegate)计算正确的单元格大小,通过获取集合 View 的宽度并使用大小调整单元格来计算单元格的高度。

在您的 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
}

这将导致集合 View 根据封闭的集合 View 设置单元格的宽度,然后要求调整大小的单元格计算高度。

第二部分是让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
 }

(此代码改编自 Apple 示例代码,来自“高级收藏 View ”上的 WWDC2014 session 的示例代码。)

有几点需要注意。它使用 layoutIfNeeded() 来强制整个单元格的布局,以便计算和设置标签的宽度。但这还不够。相信你也需要设置preferredMaxLayoutWidth以便标签将在自动布局中使用该宽度。只有这样你才能使用systemLayoutSizeFittingSize为了让单元格在考虑标签的同时计算其高度。

我喜欢这种方法吗?不!!感觉太复杂了,而且布局了两次。但是只要性能不成为问题,我宁愿在运行时执行两次布局而不是必须在代码中定义它两次,这似乎是唯一的其他选择。

我希望最终自定大小的单元格会以不同的方式工作,这一切都会变得更简单。

Example project在工作中展示它。

但为什么不只使用自定大小的单元格呢?

从理论上讲,iOS8 的“自动调整单元格大小”的新功能应该使这成为不必要的。如果你已经定义了一个带有自动布局 (AL) 的单元格,那么集合 View 应该足够智能,可以让它自行调整大小并正确布局。在实践中,我还没有看到任何可以将其与多行标签一起使用的示例。我想 this部分原因是自调整单元格机制仍然存在缺陷。

但我敢打赌,这主要是因为自动布局和标签通常很棘手,即 UILabels 需要一个基本的两步布局过程。我不清楚如何使用自动调整大小的单元来执行这两个步骤。

就像我说的,这真的是一个不同布局的工作。流布局本质的一部分是它定位具有大小的事物,而不是固定宽度并让它们选择高度。

那么 preferredLayoutAttributesFittingAttributes: 呢?
preferredLayoutAttributesFittingAttributes:我认为,方法是一个红鲱鱼。那只能与新的自定大小单元机制一起使用。因此,只要该机制不可靠,这就不是答案。

systemlayoutSizeFittingSize: 怎么了?

你是对的,文档令人困惑。
systemLayoutSizeFittingSize: 上的文档和 systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:两者都建议你应该只通过 UILayoutFittingCompressedSizeUILayoutFittingExpandedSizetargetSize .但是,方法签名本身、标题注释和函数的行为表明它们正在响应 targetSize 的确切值。参数。

事实上,如果你设置了UICollectionViewFlowLayoutDelegate.estimatedItemSize ,为了启用新的自调整单元格机制,该值似乎作为 targetSize 传入。和 UILabel.systemLayoutSizeFittingSize似乎返回与 UILabel.sizeThatFits 完全相同的值.鉴于 systemLayoutSizeFittingSize 的论点,这是可疑的。应该是一个粗略的目标和参数 sizeThatFits:应该是最大外接尺寸。

更多资源

而可悲的是认为这样的常规要求应要求“的科研资源”,我认为它。很好的例子和讨论是:
  • 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 的代码,“具有集合 View 的高级用户界面”
  • https://stackoverflow.com/questions/26143591/

    相关文章:

    ios - UIAlertController 自定义字体、大小、颜色

    objective-c - 在 Objective-C 中获取对象属性列表

    iphone - 如何制作 URL/电话可点击的 UILabel?

    ios - 在输入击键时获取 UITextField 的值?

    objective-c - 有条件地从 AppDelegate Storyboard中的不同位置开始

    ios - 了解 ibecon 距离

    objective-c - 使用 BOOL 属性

    objective-c - iOS 应用程序 : how to clear notification

    objective-c - 将 NSURL 转换为本地文件路径

    objective-c - 检查方法是否存在