是否应该将UICollectionViewCell子类的contentView.translatesAutoResizingMaskToConstraints设置为"false"? [英] Should the contentView.translatesAutoResizingMaskToConstraints of a UICollectionViewCell subclass be set to `false`?

查看:64
本文介绍了是否应该将UICollectionViewCell子类的contentView.translatesAutoResizingMaskToConstraints设置为"false"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL; DR

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

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


我正在尝试使用自定义尺寸自动布局单元格创建一个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优先级(并且对尾随约束进行相同操作),则可以解决此问题.同样,只要我在每个方向上都没有1000个优先级,就可以选择顶部+顶部"或宽度+高度".至少必须放弃"初始布局.

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

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

通过将其设置为false,它实际上摆脱了NSAutoresizingMaskLayoutConstraint约束,并且我们不再有冲突.另一方面,由于contentView最初并未设置自动布局,因此无论您如何更改其框架,其超级视图(单元格)都永远不会更新其大小,因为没有任何推动"操作反对.

尽管如此,该解决方案还是可以神奇地工作的.我假设在某个时候该单元格会查看contentView的框架并决定嘿,您将我设置为该尺寸,这可能意味着您也希望该单元格也为该尺寸",并会为您调整单元格的大小.

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

有没有更好的解决方案来解决这个问题?应该使用以上哪一项?上面提到的两种解决方案是否有不利之处,或者它们本质上是相同的,并且使用哪一种都不重要?


注意:

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

不起作用的东西:

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

非解决方案:

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

解决方案

在测试完所有内容后,我注意到至少有一个保留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
  }
}

(注意:由于具有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
}

TL;DR

When trying to size UICollectionViewCells via auto layout, you can easily get auto layout warnings with even a simple example.

Should we be setting contentView.translatesAutoResizingMaskToConstraints = false to get rid of them?


I'm trying to create a UICollectionView with self sizing auto layout cells.

In viewDidLoad:

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

My cell is very basic. It's a single blue view with width and height 75 for testing purposes. The constraints are created by pinning the view to the superview on all 4 edges, and giving it a height and width.

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
  }
}

When running the code, I get two errors about not being able to simultaneously satisfy constraints:

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.

(The second error about the vertical constraints is omitted but it's nearly identical.)

These errors make sense. Essentially, the contentView (0x7fed8a90c2f0) has translatesAutoresizingMask = true, thus its bounds set to 10x10 (from the estimated size) creates that NSAutoresizingMaskLayoutConstraint constraint. There's no way the view can be both 10 pixels wide and 75 pixels wide, so it outputs an error.

I tried several different things to resolve this problem, with only 2 of them seeming to work.

Solution 1: Set at least one of the constraints to 999 or less

In this case, if I change the constraint that pins the bottom of the blue view to the bottom of the contentView to 999 priority (and do the same with the trailing constraint), it resolves the problem. Similarly, I could have chosen Top+Leading, or Width+Height so long as I don't have 1000 priorities in every direction. At least one has to "give" for the initial layout.

Although it may sound like this would cause the view to have an incorrect height/width, it actually appears to be the correct size.

Solution 2: Set contentView.translatesAutoresizingMaskIntoConstraints = false

By setting this to false, it essentially gets rid of the NSAutoresizingMaskLayoutConstraint constraint, and we no longer have a conflict. On the other hand, since the contentView is not set up with auto layout to begin with, it's as though no matter how you change its frame, its superview (the cell) is never going to update its size because there is nothing "pushing" back against it.

Nevertheless, this solution somehow magically works. I assume that at some point the cell looks at the contentView's frame and decides "hey you set me to this size, that probably means you want the cell to be this size as well" and takes care of resizing the cell for you.

Which solution should we use?

Are there better solutions to this problem? Which of the above should be used? Are there any downsides to the two solutions mentioned above, or are they essentially the same and it doesn't matter which one you use?


Notes:

  • The reason it is harder to see in examples such as DGSelfSizingCollectionViewCells is because they are relying on intrinsicContentSize + contentCompressionResistancePriority. If you get rid of my width+height constraints, replace them with 1000 setContentCompressionResistancePriority calls (horizontal+vertical), and use something with an intrinsic size (e.g. a label with text), you will no longer see the auto layout errors. This implies that width@1000 behaves differently than an intrinsicWidth + contentCompressionResistance@1000. Perhaps the time a view gets its intrinsic width is after it's okay to modify the the frame of the content view.

Things that didn't work:

  • I noticed that when I create cells via a storyboard, I usually never run in to this problem. When I examined the differences in the view of a cell created via a storyboard and one created programmatically, I noticed that the autoresizingMask was RM+BM (36) in the storyboard, but 0 when created via code. I tried manually setting the autoresizingMask of my blue view to 36, but that didn't work. The real reason that the storyboard solution works is that usually the constraints you create are already perfectly set in the storyboard (otherwise you would have storyboard errors that need to be fixing). As part of fixing these errors, you may change the bounds of the cell. Since it calls init?(coder:), the bounds of the cell are already configured correctly. So even though the contentView.autoresizingMask = true, there are no conflicts since it's already defined at the correct size.
  • In the linked guide, it links to this post when talking about creating constraints. It mentions that constraints should be created in updateConstraints. However, it sounds like this was Apple's original recommendations and that they are no longer recommending this convention for most of your constraints. Nevertheless, since that article says to create them in updateConstraints I tried to no avail. I got the same results. This is because the frame wasn't updated by the time updateConstraints was called (it was still 10x10).
  • I've noticed similar problems when you choose a estimated size that is too small. I've fixed it in the past by increasing the estimated size. In this case I tried 100x100, but it still caused the same errors. This makes sense... a view can't be both 100 wide and 75 wide at the same time. (Note that I consider setting it to 75x75 a non-solution. See below for more info.)

Non-solutions:

  • Setting the estimated size to 75x75. Although this will resolve the errors for this simple example, it won't resolve the problem for cells that truly are dynamically sized. I want a solution that'll work everywhere.
  • Returning the exact dimensions of the cell in collectionView(_:layout:sizeForItemAtIndexPath:) (e.g. by creating a sizing cell). I want a solution that works with my cell directly without needing any bookkeeping or extra calculation.

解决方案

After testing things out, I noticed at least one reason to keep contentView.translatesAutoresizingMaskToConstraints = true.

If you use preferredLayoutAttributesFittingAttributes(_:) to alter the dimensions of a UICollectionViewCell, it works by sizing the contentView to the correct dimension. If you set contentView.translatesAutoresizingMaskToConstraints = false, you'll lose this functionality.

Therefore, I recommend using Solution 1 (altering at least one constraint in each dimension to be non-required). In fact, I created a wrapper for UICollectionViewCell that will handle both the required 999 constraint, and a way to get preferred height or width to function correctly.

By using this wrapper, you won't need to remember the intricacies of getting the contentView in a UICollectionViewCell to behave properly.


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
  }
}

(Note: the init method is required due to a bug with generic subclasses of UICollectionViewCell.)

To register:

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

To use:

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
}

这篇关于是否应该将UICollectionViewCell子类的contentView.translatesAutoResizingMaskToConstraints设置为"false"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆