I am trying to implement a collection view where each cell has a variable number of UIButton instances, placed horizontally end to end.
Because the number of buttons is dynamic, I am instantiating the buttons at runtime and adding the auto layout constraints programmatically (first time attempting this).
This is my code:
class MyCustomCell: UICollectionViewCell
{
var buttonTitles:[String]?
var buttons = [UIButton]()
override func didMoveToSuperview()
{
super.didMoveToSuperview()
guard let titles = buttonTitles else {
return
}
for button in buttons {
button.removeFromSuperview()
}
buttons.removeAll()
self.translatesAutoresizingMaskIntoConstraints = false
// FIRST LOOP: CREATE/ADD BUTTONS:
for (index, title) in titles.enumerate(){
let button = UIButton(type: UIButtonType.Custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(title, forState: .Normal)
button.tag = index
addSubview(button)
buttons.append(button)
}
// SECOND LOOP: ADD CONSTRAINTS:
for (index, button) in buttons.enumerate(){
// Vertical Constaints (common to all buttons)
button.addConstraint(
NSLayoutConstraint(
item: self,
attribute: NSLayoutAttribute.TopMargin,
relatedBy: NSLayoutRelation.Equal,
toItem: button,
attribute: NSLayoutAttribute.Top,
multiplier: 1,
constant: 0
)
)
button.addConstraint(
NSLayoutConstraint(
item: self,
attribute: NSLayoutAttribute.BottomMargin,
relatedBy: NSLayoutRelation.Equal,
toItem: button,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0
)
)
// Horizontal Constriants
if index == 0 {
// First - Constrain left to parent (self):
button.addConstraint(
NSLayoutConstraint(
item: self,
attribute: NSLayoutAttribute.LeftMargin,
relatedBy: NSLayoutRelation.Equal,
toItem: button,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: 0
)
)
}
else {
// Not first - Constrain left to previous button:
let previousButton = self.subviews[index - 1]
button.addConstraint(
NSLayoutConstraint(
item: previousButton,
attribute: NSLayoutAttribute.LeftMargin,
relatedBy: NSLayoutRelation.Equal,
toItem: button,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: 0
)
)
if index == (titles.count - 1) {
// Last: Constrain right
button.addConstraint(
NSLayoutConstraint(
item: self,
attribute: NSLayoutAttribute.RightMargin,
relatedBy: NSLayoutRelation.Equal,
toItem: button,
attribute: NSLayoutAttribute.Right,
multiplier: 1,
constant: 0
)
)
}
}
}
}
}
I am getting this exception thrown when I try to add the very first constraint:
The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7f9702d19610 MyAppName.MyCustomCell:0x7f9703a690e0.topMargin == UIView:0x7f9702d163e0.top>
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
2015-10-06 18:39:58.863 Meeting Notes[15219:1817298] View hierarchy unprepared for constraint.
Constraint: <NSLayoutConstraint:0x7f9702d19610 MyAppName.MyCustomCell:0x7f9703a690e0.topMargin == UIView:0x7f9702d163e0.top>
Container hierarchy:
<UIView: 0x7f9702d163e0; frame = (0 0; 97 60); gestureRecognizers = <NSArray: 0x7f96fb64b5c0>; layer = <CALayer: 0x7f96fb606880>>
View not found in container hierarchy: <MyAppName.MyCustomCell: 0x7f9703a690e0; baseClass = UICollectionViewCell; frame = (103 0; 97 60); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x7f96fb64bcd0>>
That view's superview: <UICollectionView: 0x7f96fb8cc800; frame = (0 172; 1024 596); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x7f9701f813f0>; layer = <CALayer: 0x7f9701f865c0>; contentOffset: {0, 0}; contentSize: {1024, 182}> collection view layout: <UICollectionViewFlowLayout: 0x7f9701f8df50>
(lldb)
As discussed here and other similar questions, this happens when the views involved in the constraint are not in a parent-child relationship; however, I have already added all the subviews before adding any constraint (I split the loop into two). Also, the whole code runs inside the cell's didMovetoSuperview
, so the cell itself isn't orphaned...
What am I missing?