1

My Notification Center widget will need to have a dynamic height based on the content it contains. I have a simple interface - a single UILabel with a UICollectionView underneath. (The collection view will grow in height based on the size I provide for the cells in the flow layout.)

What constraints are necessary in order to properly specify the height of the widget?

I thought it would be enough to provide a Top constraint on the label to fix it to the view's Top, specify the collection view's Top aligns with the label's Bottom, then provide a fixed Height for the collection view (which is updated when the itemSize changes), then align the collection view's Bottom to the view's Bottom. But this results in two broken constraints - collection view's height and the vertical constraint between the label and the collection view.

let label = //...
label.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(label)
self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: 10))
self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1, constant: 25))
self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Leading, relatedBy: .Equal, toItem: self.view, attribute: .LeadingMargin, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Trailing, relatedBy: .Equal, toItem: self.view, attribute: .TrailingMargin, multiplier: 1, constant: 0))

let collectionView = //...
collectionView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(collectionView)
self.view.addConstraint(NSLayoutConstraint(item: collectionView, attribute: .Leading, relatedBy: .Equal, toItem: label, attribute: .Leading, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: collectionView, attribute: .Trailing, relatedBy: .Equal, toItem: label, attribute: .Trailing, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: collectionView, attribute: .Top, relatedBy: .Equal, toItem: label, attribute: .Bottom, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: collectionView, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1, constant: 0))
self.collectionViewHeightConstraint = NSLayoutConstraint(item: collectionView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1, constant: 100)
collectionView.addConstraint(self.collectionViewHeightConstraint)

//later on
let flowLayout = collectionView.collectionViewLayout as UICollectionViewFlowLayout
flowLayout.itemSize = //some new size
self.collectionViewHeightConstraint.constant = flowLayout.itemSize.height * numberOfRows

The problem:

Unable to simultaneously satisfy constraints.
"<NSLayoutConstraint:0x6080000998c0 V:|-(10)-[UILabel:0x6000001db300]   (Names: '|':UIView:0x60800018f700 )>",
"<NSLayoutConstraint:0x608000099e60 V:[UILabel:0x6000001db300(25)]>",
"<NSLayoutConstraint:0x60800009a2c0 V:[UILabel:0x6000001db300]-(0)-[UICollectionView:0x7ff94b02c200]>",
"<NSLayoutConstraint:0x60800009a310 UICollectionView:0x7ff94b02c200.bottom == UIView:0x60800018f700.bottom>",
"<NSLayoutConstraint:0x60800009a360 V:[UICollectionView:0x7ff94b02c200(100)]>",
"<NSLayoutConstraint:0x60800009a4f0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x60800018f700(667)]>"
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x60800009a360 V:[UICollectionView:0x7ff94b02c200(100)]>

And

Unable to simultaneously satisfy constraints.
"<NSLayoutConstraint:0x6080000998c0 V:|-(10)-[UILabel:0x6000001db300]   (Names: '|':UIView:0x60800018f700 )>",
"<NSLayoutConstraint:0x608000099e60 V:[UILabel:0x6000001db300(25)]>",
"<NSLayoutConstraint:0x60800009a2c0 V:[UILabel:0x6000001db300]-(0)-[UICollectionView:0x7ff94b02c200]>",
"<NSLayoutConstraint:0x60800009a310 UICollectionView:0x7ff94b02c200.bottom == UIView:0x60800018f700.bottom>",
"<NSLayoutConstraint:0x60800009a4f0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x60800018f700(0)]>"
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x60800009a2c0 V:[UILabel:0x6000001db300]-(0)-[UICollectionView:0x7ff94b02c200]>
Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • You show which constraint the system decides to break, but that's basically arbitrary. You need to show the whole list of constraints which conflict. – Ken Thomases Mar 07 '15 at 09:27

2 Answers2

1

In the first set of conflicting constraints, the problem is actually that your view ends up shorter than the Notification Center seems to want to make it. Here are the constraints, slightly re-ordered so they read from top to bottom:

<NSLayoutConstraint:0x6080000998c0 V:|-(10)-[UILabel:0x6000001db300]   (Names: '|':UIView:0x60800018f700 )>
<NSLayoutConstraint:0x608000099e60 V:[UILabel:0x6000001db300(25)]>
<NSLayoutConstraint:0x60800009a2c0 V:[UILabel:0x6000001db300]-(0)-[UICollectionView:0x7ff94b02c200]>
<NSLayoutConstraint:0x60800009a360 V:[UICollectionView:0x7ff94b02c200(100)]>
<NSLayoutConstraint:0x60800009a310 UICollectionView:0x7ff94b02c200.bottom == UIView:0x60800018f700.bottom>

So, the container view (UIView:0x60800018f700) is being required to be 10 + 25 + 100 == 135 points tall. That conflicts with this constraint imposed by the Notification Center:

<NSLayoutConstraint:0x60800009a4f0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x60800018f700(667)]>

which dictates a height of 667 points.

The Notification Center wants the container view to be taller than your constraints are allowing it to be. You could change your constraint between the collection view's bottom and its superview's bottom to be an inequality — the superview's bottom is greater-than-or-equal-to the collection view's bottom. Alternatively, you can change the constraint on the height of the collection view to be an inequality — the height is greater-than-or-equal-to 100.

However, the second case is the opposite problem. In this case, the Notification Center has imposed a height of 0 on the container view. Your constraints require the height to be non-zero, so that's a conflict. Your constraints, slightly re-ordered:

<NSLayoutConstraint:0x6080000998c0 V:|-(10)-[UILabel:0x6000001db300]   (Names: '|':UIView:0x60800018f700 )>
<NSLayoutConstraint:0x608000099e60 V:[UILabel:0x6000001db300(25)]>
<NSLayoutConstraint:0x60800009a2c0 V:[UILabel:0x6000001db300]-(0)-[UICollectionView:0x7ff94b02c200]>
<NSLayoutConstraint:0x60800009a310 UICollectionView:0x7ff94b02c200.bottom == UIView:0x60800018f700.bottom>

The container has to be 10 + 25 + height of collection view (implicitly >=0) >= 35 points tall. The Notification Center's constraint:

<NSLayoutConstraint:0x60800009a4f0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x60800018f700(0)]>

It's not clear to me why the Notification Center is changing the imposed height like this. This seems to occur after the first conflict is resolved by ignoring the collection view height, because that's omitted from this log.

Try fixing the first and see if the second goes away. If it doesn't, you can reduce the priority of the constraint between the bottom of the collection view and the bottom of the superview to, say, 900. That's still nearly required and so should keep your layout when it's possible, but it's no longer actually required — it's optional — so it can't contribute to unsatisfiable constraints.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
0

Change self.view.addConstraint(self.collectionViewHeightConstraint) to collectionView.addConstraint(self.collectionViewHeightConstraint), because this constraint is for the collectionView, not its superView.

gabbler
  • 13,626
  • 4
  • 32
  • 44
  • Thanks, unfortunately that still doesn't resolve the broken constraints. I'll post all those. – Jordan H Mar 07 '15 at 20:58
  • 1
    Try to add this: `self.collectionViewHeightConstraint.priority = 999`, this lower its priority to give precedence to `UIView-Encapsulated-Layout-Height` constraint, which is generated by the system. You can set its priority value to something lower than 1000. CollectionView's height limit is around 510 in a iPhone 6 simulator, if you set its height to greater than that, it is still 510. – gabbler Mar 08 '15 at 02:29
  • Thanks for that, that was indeed part of the problem. – Jordan H Mar 08 '15 at 19:56