【问题标题】:Should the contentView.translatesAutoResizingMaskToConstraints of a UICollectionViewCell subclass be set to `false`?UICollectionViewCell 子类的 contentView.translatesAutoResizingMaskToConstraints 是否应该设置为“false”?
【发布时间】:2016-11-06 16:38:29
【问题描述】:

TL;DR

当尝试通过自动布局调整 UICollectionViewCells 的大小时,即使是一个简单的示例,您也可以轻松获得自动布局警告。

我们应该设置contentView.translatesAutoResizingMaskToConstraints = false 来摆脱它们吗?


我正在尝试使用self sizing auto layout cells 创建一个 UICollectionView。

在 viewDidLoad 中:

let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 10, height: 10)
collectionView.collectionViewLayout = layout

我的手机非常基础。这是一个单一的蓝色视图,宽度和高度为 75,用于测试目的。约束是通过将视图固定到所有 4 条边上的父视图并为其指定高度和宽度来创建的。

class MyCell: UICollectionViewCell {
  override init(frame: CGRect) {
    view = UIView()

    super.init(frame: frame)

    view.backgroundColor = UIColor.blueColor()
    contentView.addSubview(view)

    installConstraints()
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  var view: UIView

  func installConstraints() {
    view.translatesAutoresizingMaskIntoConstraints = false

    var c: NSLayoutConstraint

    // pin all edges
    c = NSLayoutConstraint(item: contentView, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0)
    c.active = true
    c = NSLayoutConstraint(item: contentView, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0)
    c.active = true
    c = NSLayoutConstraint(item: contentView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)
    c.active = true
    c = NSLayoutConstraint(item: contentView, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0)
    c.active = true

    // set width and height
    c = NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 75)
    c.active = true
    c = NSLayoutConstraint(item: view, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 75)
    c.active = true
  }
}

运行代码时,我收到两个关于无法同时满足约束的错误:

Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
    (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x7fed88c1bac0 h=--& v=--& H:[UIView:0x7fed8a90c2f0(10)]>",
    "<NSLayoutConstraint:0x7fed8ab770b0 H:[UIView:0x7fed8a90bbe0(75)]>",
    "<NSLayoutConstraint:0x7fed8a90d610 UIView:0x7fed8a90c2f0.leading == UIView:0x7fed8a90bbe0.leading>",
    "<NSLayoutConstraint:0x7fed8ab005f0 H:[UIView:0x7fed8a90bbe0]-(0)-|   (Names: '|':UIView:0x7fed8a90c2f0 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7fed8ab770b0 H:[UIView:0x7fed8a90bbe0(75)]>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

(省略了关于垂直约束的第二个错误,但几乎相同。)

这些错误是有道理的。本质上,contentView (0x7fed8a90c2f0) 具有translatesAutoresizingMask = true,因此其边界设置为 10x10(根据估计大小)创建了 NSAutoresizingMaskLayoutConstraint 约束。视图不可能同时是 10 像素宽和 75 像素宽,因此会输出错误。

我尝试了几种不同的方法来解决这个问题,但其中只有 2 种似乎有效。

解决方案 1:将至少一个约束设置为 999 或更少

在这种情况下,如果我将蓝色视图底部固定到 contentView 底部的约束更改为 999 优先级(并对尾随约束执行相同的操作),它可以解决问题。同样,我可以选择 Top+Leading 或 Width+Height,只要我在每个方向上都没有 1000 个优先级。至少一个人必须为初始布局“放弃”。

虽然这听起来可能会导致视图的高度/宽度不正确,但它实际上看起来是正确的大小。

解决方案2:设置contentView.translatesAutoresizingMaskIntoConstraints = false

通过将其设置为false,它基本上摆脱了NSAutoresizingMaskLayoutConstraint 约束,我们不再有冲突。另一方面,由于 contentView 一开始没有设置自动布局,就好像无论你如何改变它的框架,它的超级视图(单元格)永远不会更新它的大小,因为没有什么“推动”反对它。

尽管如此,这个解决方案以某种方式神奇地起作用。我假设在某些时候单元格会查看 contentView 的框架并决定“嘿,您将我设置为这个大小,这可能意味着您希望单元格也有这个大小”并负责为您调整单元格的大小。

我们应该使用哪种解决方案?

这个问题有更好的解决方案吗?应该使用以上哪个?上面提到的两种解决方案是否有任何缺点,或者它们本质上是相同的,你使用哪一种都没有关系?


注意事项:

  • DGSelfSizingCollectionViewCells 等示例中更难看到的原因是它们依赖于intrinsicContentSize + contentCompressionResistancePriority。如果您摆脱我的宽度+高度约束,将它们替换为 1000 个 setContentCompressionResistancePriority 调用(水平+垂直),并使用具有固有大小的东西(例如带有文本的标签),您将不再看到自动布局错误。这意味着width@1000 的行为与intrinsicWidth + contentCompressionResistance@1000 的行为不同。也许视图获得其固有宽度的时间是在可以修改内容视图的框架之后。

没用的东西:

  • 我注意到,当我通过情节提要创建单元格时,我通常不会遇到这个问题。当我检查通过情节提要创建的单元格与以编程方式创建的单元格的view 之间的差异时,我注意到情节提要中的autoresizingMask 是 RM+BM (36),但通过代码创建时为 0。我尝试手动将蓝色视图的autoresizingMask 设置为 36,但这不起作用。故事板解决方案起作用的真正原因是通常您创建的约束已经在故事板中完美设置(否则您将遇到需要修复的故事板错误)。作为修复这些错误的一部分,您可以更改单元格的边界。由于它调用init?(coder:),因此单元格的边界已经正确配置。因此,即使 contentView.autoresizingMask = true,也没有冲突,因为它已经以正确的大小定义。
  • 在链接指南中,当谈到创建约束时,它链接到this post。它提到应该在updateConstraints 中创建约束。但是,这听起来像是 Apple 最初的建议,并且它们是 no longer recommending 这个约定对于您的大多数约束。尽管如此,因为那篇文章说要在updateConstraints 中创建它们,所以我试图无济于事。我得到了同样的结果。这是因为在调用 updateConstraints 时框架并未更新(它仍然是 10x10)。
  • 当您选择的估计尺寸太小时,我注意到了类似的问题。我过去通过增加估计大小来修复它。在这种情况下,我尝试了 100x100,但仍然导致相同的错误。这是有道理的……一个视图不能同时是 100 宽和 75 宽。 (请注意,我认为将其设置为 75x75 是一种非解决方案。有关详细信息,请参阅下文。)

非解决方案:

  • 将估计大小设置为 75x75。尽管这将解决这个简单示例的错误,但它不会解决真正动态调整大小的单元格的问题。我想要一个适用于任何地方的解决方案。
  • 返回collectionView(_:layout:sizeForItemAtIndexPath:) 中单元格的确切尺寸(例如,通过创建大小调整单元格)。我想要一个直接适用于我的单元的解决方案,无需任何簿记或额外计算。

【问题讨论】:

  • 我发现当估计尺寸大于自动布局确定的尺寸时,问题就消失了。

标签: ios autolayout uicollectionview uicollectionviewcell


【解决方案1】:

经过测试,我注意到至少有一个理由保留contentView.translatesAutoresizingMaskToConstraints = true

如果您使用preferredLayoutAttributesFittingAttributes(_:) 来更改UICollectionViewCell 的尺寸,它的工作原理是将 contentView 调整为正确的尺寸。如果您设置contentView.translatesAutoresizingMaskToConstraints = false,您将失去此功能。

因此,我建议使用解决方案 1(将每个维度中的至少一个约束更改为不需要)。事实上,我为 UICollectionViewCell 创建了一个包装器,它可以处理所需的 999 约束,以及一种获得首选高度或宽度以正常工作的方法。

通过使用此包装器,您无需记住如何让 UICollectionViewCell 中的 contentView 正常运行。


class CollectionViewCell<T where T: UIView>: UICollectionViewCell {

  override init(frame: CGRect) {
    preferredHeight = nil
    preferredWidth = nil

    super.init(frame: frame)
  }

  var preferredWidth: CGFloat?
  var preferredHeight: CGFloat?
  private(set) var view: T?

  func initializeView(view: T) {
    assert(self.view == nil)
    self.view = view

    contentView.addSubview(view)

    view.translatesAutoresizingMaskIntoConstraints = false

    var constraint: NSLayoutConstraint

    constraint = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 0)
    constraint.active = true
    constraint = NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: contentView, attribute: .Leading, multiplier: 1, constant: 0)
    constraint.active = true

    // Priority must be less than 1000 to prevent errors when installing
    // constraints in conjunction with the contentView's autoresizing constraints.
    let NonRequiredPriority: UILayoutPriority = UILayoutPriorityRequired - 1

    constraint = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1, constant: 0)
    constraint.priority = NonRequiredPriority
    constraint.active = true
    constraint = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: contentView, attribute: .Trailing, multiplier: 1, constant: 0)
    constraint.priority = NonRequiredPriority
    constraint.active = true
  }

  override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let newLayoutAttributes = super.preferredLayoutAttributesFittingAttributes(layoutAttributes)

    if let preferredHeight = preferredHeight {
      newLayoutAttributes.bounds.size.height = preferredHeight
    }
    if let preferredWidth = preferredWidth {
      newLayoutAttributes.bounds.size.width = preferredWidth
    }

    return newLayoutAttributes
  }
}

(注意:由于bug with generic subclasses of UICollectionViewCell,所以需要init方法。)

注册:

collectionView.registerClass(CollectionViewCell<UIView>.self, forCellWithReuseIdentifier: "Cell")

使用方法:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CollectionViewCell<UILabel>

  if cell.view == nil {
    cell.initializeView(UILabel())
  }

  cell.view!.text = "Content"
  cell.preferredHeight = collectionView.bounds.height

  return cell
}

【讨论】:

    猜你喜欢
    • 2012-05-07
    • 2016-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-07
    • 1970-01-01
    相关资源
    最近更新 更多