如果将UICollectionViewCell子类的contentView.translatesAutoResizingMaskToConstraints设置为“false”?

TL; DR

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

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


我正在尝试创build一个UICollectionView 自定义自动布局单元格 。

在viewDidLoad中:

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

我的细胞是非常基本的。 这是一个宽度和高度为75的单一蓝色视图,用于testing目的。 约束是通过将视图固定在所有4个边上的超视图来创build的,并给它一个高度和宽度。

 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 ,因此将其边界设置为10×10(根据估计大小)创buildNSAutoresizingMaskLayoutConstraint约束。 视图不可能既是10像素宽又是75像素宽,所以它输出一个错误。

我尝试了几种不同的方法来解决这个问题,其中只有两个看起来有效。

解决scheme1:将至less一个约束设置为999或更less

在这种情况下,如果我将蓝色视图的底部到contentView的底部的约束更改为999优先级(对跟踪约束执行相同操作),则可以解决问题。 同样,只要我没有1000个优先级,我可以selectTop + Leading或者Width + Height。 至less有一个人必须“给”最初的布局。

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

解决scheme2:设置contentView.translatesAutoresizingMaskIntoConstraints = false

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

尽pipe如此,这个解决scheme神奇地工作。 我假定在某个时候,单元看着contentView的框架,并决定“嘿,你把我设置成这个大小,这可能意味着你希望单元格也是这个大小”,并负责为你调整单元格的大小。

我们应该使用哪种解决scheme

这个问题有更好的解决scheme吗? 上面哪个应该使用? 上面提到的两个解决scheme有什么缺点,或者它们基本上是一样的,你使用哪一个并不重要?


笔记:

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

事情没有工作:

  • 我注意到,当我通过故事板创build单元格时,我通常不会遇到这个问题。 当我view通过故事板创build的单元格和编程创build的单元格的差异时,我发现故事板中的autoresizingMask是RM + BM(36),但是通过代码创build时为0。 我尝试手动将我的蓝色视图的autoresizingMask设置为36,但是这并不起作用。 故事板解决scheme工作的真正原因是,通常情况下,您创build的约束条件已经完全在故事板中设置(否则,您将有故事板错误需要修复)。 作为修复这些错误的一部分,您可以更改单元格的边界。 由于它调用init?(coder:) ,所以单元格边界已经正确configuration了。 所以即使contentView.autoresizingMask = true ,也没有冲突,因为它已经被定义为正确的大小。
  • 在链接的指南中,当谈到创build约束时,链接到这个post 。 它提到了约束应该在updateConstraints创build。 不过,这听起来像是苹果公司原来的build议,他们不再推荐这个约定的大部分约束。 尽pipe如此,自从那篇文章说在updateConstraints创build它们,我试图无济于事。 我得到了同样的结果。 这是因为在updateConstraints被调用的时候框架没有被更新(它仍然是10×10)。
  • 当你select一个太小的估计尺寸时,我发现了类似的问题。 我通过增加估计的大小来修复它。 在这种情况下,我尝试了100×100,但它仍然导致相同的错误。 这是有道理的…一个观点不能同时是100宽和75宽。 (请注意,我认为将其设置为75×75是非解决scheme,请参阅下面的更多信息。)

非解决scheme:

  • 将估计大小设置为75×75。 虽然这将解决这个简单例子的错误,但它不能解决真正dynamicresize的单元的问题。 我想要一个能在任何地方工作的解决scheme
  • collectionView(_:layout:sizeForItemAtIndexPath:)返回单元格的确切尺寸(例如,通过创build尺寸单元格)。 我想要一个可以直接与我的单元一起工作的解决scheme,而不需要任何簿记或额外的计算

经过testing后,我注意到至less有一个原因保持contentView.translatesAutoresizingMaskToConstraints = true

如果使用preferredLayoutAttributesFittingAttributes(_:)来更改UICollectionViewCell的尺寸,则可以通过调整contentView的大小来适应正确的尺寸。 如果你设置contentView.translatesAutoresizingMaskToConstraints = false ,你将失去这个function。

因此,我build议使用解决scheme1(每个维度中至less有一个约束是不需要的)。 实际上,我为UICollectionViewCell创build了一个包装器,它将处理所需的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 } } 

(注意:由于UICollectionViewCell的generics子类存在一个错误,因此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 }