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个似乎有效。
在这种情况下,如果我更改将蓝色视图底部固定到contentView底部的约束为999优先级(并对尾随约束执行相同操作),则可以解决问题。同样,我可以选择Top + Leading,或Width + Height,只要我不在每个方向都有1000个优先级。至少有一个人必须&#34;给予&#34;对于初始布局。
虽然听起来可能会导致视图的高度/宽度不正确,但实际上看起来大小正确。
contentView.translatesAutoresizingMaskIntoConstraints = false
通过将其设置为false
,它基本上摆脱了NSAutoresizingMaskLayoutConstraint
约束,我们不再有冲突。另一方面,由于contentView没有设置为自动布局,所以无论你如何改变它的框架,它的超级视图(单元格)永远不会更新它的大小,因为它有什么都没有&#34;推动&#34;反对它。
然而,这个解决方案在某种程度上神奇地起作用。我假设在某个时刻,单元格会查看contentView的框架并决定&#34;嘿,你把我设置为这个大小,这可能意味着你希望单元格也是这样大小的#34;并负责为您调整单元格大小。
这个问题有更好的解决方案吗?应该使用以上哪个?上面提到的两种解决方案是否存在任何缺点,或者它们基本相同,并且您使用哪种解决方案并不重要?
注意:
width@1000
与内在宽度+ contentCompressionResistance@1000
的行为不同。也许视图获得其固有宽度的时间是可以修改内容视图的框架。那些没有用的东西:
view
和以编程方式创建的单元格的差异时,我注意到故事板中的autoresizingMask
是RM + BM(36),但是通过代码创建时为0 。我尝试手动将我的蓝色视图的autoresizingMask
设置为36,但这并不起作用。故事板解决方案的真正原因在于,您创建的约束通常已在故事板中完美设置(否则您将需要修复故事板错误)。作为修复这些错误的一部分,您可以更改单元格的边界。由于它调用init?(coder:)
,因此已正确配置了单元格的边界。所以即使是contentView.autoresizingMask = true
,也没有冲突,因为它已经以正确的大小定义了。updateConstraints
中创建约束。然而,听起来这是Apple的原始建议,并且对于大多数约束,它们都是no longer recommending这个约定。然而,由于该文章说要在updateConstraints
中创建它们,我试图无济于事。我得到了相同的结果。这是因为在调用updateConstraints时它没有更新帧(它仍然是10x10)。非解决方案:
collectionView(_:layout:sizeForItemAtIndexPath:)
中返回单元格的确切尺寸(例如,通过创建尺寸单元格)。我想要一个直接适用于我的手机的解决方案,无需任何记账或额外的计算。答案 0 :(得分:3)
在测试完毕后,我发现至少有一个理由要保持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
}