2

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?

Community
  • 1
  • 1
Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189

1 Answers1

6

See the error message:

When added to a view, the constraint's items must be descendants of that view (or the view itself)

In your case, all constraints should be added to self:

self.addConstraint( ...
rintaro
  • 51,423
  • 14
  • 131
  • 139