是否应将UICollectionViewCell子类的contentView.translatesAutoResizingMaskToConstraints设置为"false"?

Sen*_*ful 6 ios autolayout uicollectionview uicollectionviewcell

TL; DR

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

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


我正在尝试使用自定义自动布局单元格创建UICollectionView .

在viewDidLoad中:

let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 10, height: 10)
collectionView.collectionViewLayout = layout
Run Code Online (Sandbox Code Playgroud)

我的细胞很基础.它是一个宽度和高度为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
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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.
Run Code Online (Sandbox Code Playgroud)

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

这些错误是有道理的.本质上,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通过故事板创建的单元格和以编程方式创建的单元格的差异时,我注意到故事板中的autoresizingMaskRM + BM(36),但是通过代码创建时为0.我尝试手动将autoresizingMask我的蓝色视图设置为36,但这不起作用.故事板解决方案的真正原因在于,您创建的约束通常已在故事板中完美设置(否则您将需要修复故事板错误).作为修复这些错误的一部分,您可以更改单元格的边界.由于它调用init?(coder:),已经正确配置了单元格的边界.因此,即使contentView.autoresizingMask = true它没有冲突,因为它已经以正确的大小定义.
  • 在链接指南中,它在谈到创建约束时链接到这篇文章.它提到应该创建约束updateConstraints.然而,听起来这是Apple的原始建议,并且他们不再为大多数约束推荐此约定.尽管如此,因为那篇文章说要创建它们updateConstraints我试图无济于事.我得到了相同的结果.这是因为在调用updateConstraints时它没有更新帧(它仍然是10x10).
  • 当你选择一个太小的估计尺寸时,我注意到了类似的问题.我通过增加估计的大小来修复它.在这种情况下,我尝试了100x100,但它仍然导致相同的错误.这是有道理的......一个视图不能同时是100宽和75宽.(请注意,我考虑将其设置为75x75非解决方案.有关详细信息,请参阅下文.)

非解决方案:

  • 将估计的大小设置为75x75.虽然这将解决这个简单示例的错误,但它不能解决真正动态调整大小的单元的问题.我想要一个能够无处不在的解决方案.
  • 返回细胞的确切尺寸collectionView(_:layout:sizeForItemAtIndexPath:)(例如通过创建尺寸单元).我想要一个直接适用于我的手机的解决方案,无需任何记账或额外计算.

Sen*_*ful 4

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

如果您用来preferredLayoutAttributesFittingAttributes(_:)更改 a 的尺寸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
  }
}
Run Code Online (Sandbox Code Playgroud)

(注意:由于UICollectionViewCell 的通用子类存在错误,因此需要 init 方法。)

注册:

collectionView.registerClass(CollectionViewCell<UIView>.self, forCellWithReuseIdentifier: "Cell")
Run Code Online (Sandbox Code Playgroud)

使用方法:

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
}
Run Code Online (Sandbox Code Playgroud)