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

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

问题描述

TL;DR

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

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

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

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

我正在尝试使用 自动调整大小的自动布局单元格创建一个 UICollectionView.

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

在 viewDidLoad 中:

In viewDidLoad:

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

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

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.)

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

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.

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

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

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

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.

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

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.

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

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.

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

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?

注意事项:

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

没用的东西:

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

非解决方案:

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

推荐答案

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

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

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

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.

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

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.

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

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

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

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

注册:

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
}

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

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