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?

1 Answer 1

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( ...
Sign up to request clarification or add additional context in comments.

4 Comments

Beside this parameter item should be swap with to item
Thanks, I misread as "one has to be a subview of the other". So, how do I add constraints between buttons?
self.addConstraint(NSLayoutConstraint(item: button1, ... toItem: button2 ...)) should works.
Not crashing anymore, thank you. But the cells aren't displaying for some reason. Will look into that now...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.