0
votes

I am a newbie swift developer, following code examples in a good iOS 9 Swift book. The book has just demonstrated how to add a constraint (in code) between two objects (label, button) that are in two different branches of the view hierarchy.

myLabel is in viewBB container, which is in the top view controller. myButton is in the top view controller, outside of viewBB.

The example code worked fine. But for an exercise, I wanted to create a second label2 inside of viewBB, and constrain its position to be offset from myLabel. So both labels (I think) are in viewBB.

Creating label2 in code and adding the constraints in code works fine.

But... if I create label2 in the interface builder, and try to remove the IB constraints and replace them with my own, the build succeeds but the app crashes with the usual "view hierarchy not prepared" error.

*** The interesting thing is the error message talks about a label and a button, suggesting that the book example code (I called it BLOCK 0 below) is the offender.

What am I doing wrong? (I've read everything I can find on the error message, including 4 posts in this forum. But I'm at a loss to know why the book code is failing.)

The error message:

2016-02-25 12:52:32.796 TwoViewConstraints[5713:1860768] The view hierarchy is not prepared for the constraint: NSLayoutConstraint:0x7c9ce990 UILabel:0x7c9dbec0'Label'.centerX == UIButton:0x7c9deed0'Button'.centerX

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.

Message from debugger: Terminated due to signal 15

Here is my code:

class ViewController: UIViewController {

@IBOutlet weak var myLabel: UILabel!
@IBOutlet weak var myButton: UIButton!
@IBOutlet weak var centerConstraint: NSLayoutConstraint!
@IBOutlet weak var viewBB: UIView!


@IBOutlet weak var lab2cxh: NSLayoutConstraint!
@IBOutlet weak var lab2cxv: NSLayoutConstraint!
@IBOutlet weak var label2: UILabel!


override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // BLOCK 0 - works fine, from the iOS9 book
    // myLabel and myButton were created with the interface builder
    // Goal is to remove an IB constraint, and replace it with one
    // that is built in code. (Because label and buttons are in 
    // different branches of the view hierarchy).
    //
    // remove auto center constraint from parent label
    viewBB.removeConstraint(centerConstraint)

    // center the label and the button across views
    let ccx = NSLayoutConstraint(item: myLabel,
        attribute: NSLayoutAttribute.CenterX,
        relatedBy: NSLayoutRelation.Equal,
        toItem: myButton,
        attribute: NSLayoutAttribute.CenterX,
        multiplier: 1.0,
        constant: 0)
    self.view.addConstraint(ccx)

//        BLOCK 1 - Create a second label object in code
//        this code works fine with the two added constraints below
//        create a second label object and add it to the view
//        let label2 = UILabel()
//        label2.translatesAutoresizingMaskIntoConstraints = false
//        label2.text="Label2"
//        viewBB.addSubview(label2)

    // BLOCK 2 - create a second label object in the interface builder
    // Suggested horiz/vert constraints are set in the IB.
    // So now I want to remove them, and replace them with my own,
    // as was done in the Swift book example code in BLOCK 0.
    // remove original label 2 constraints
    viewBB.removeConstraint(lab2cxv)
    viewBB.removeConstraint(lab2cxh)

    // adding these two constraints works fine when the label2 object
    // is created in code. But when I create label2 in the interface
    // builder and try to remove the IB constraints (as was done in the
    // first block of code), I get the error:
    //
    // ... "The view hierarchy is not prepared
    // for the constraint "Label.CenterX to Button.CenterX", which is 
    // the first block of code.
    //
    // NOTE** To me, it looks like the error is coming from the 
    // BLOCK 0 constraint, since it cites a label and a button, not
    // two labels. 
    // What am I doing wrong?
    let c2h = NSLayoutConstraint(item: label2,
        attribute: NSLayoutAttribute.CenterY,
        relatedBy: NSLayoutRelation.Equal,
        toItem: myLabel,
        attribute: NSLayoutAttribute.CenterY,
        multiplier: 1.0,
        constant: -50)
    self.viewBB.addConstraint(c2h)

    // constrain label 2 to be diagonally left of label one
    let c2v = NSLayoutConstraint(item: label2,
        attribute: NSLayoutAttribute.CenterX,
        relatedBy: NSLayoutRelation.Equal,
        toItem: myLabel,
        attribute: NSLayoutAttribute.CenterX,
        multiplier: 1.0,
        constant: -100)
    self.viewBB.addConstraint(c2v)


}
1
Try to use ccx.active = true instead of self.view.addConstraint(ccx). And use better names for your properties. - dasdom
Thank you for replying to my question. Sorry to say, but the suggested change did not work. That line of code was straight out of the book, and worked fine before I added my code below. (Granted, cx = constraint, c = centre, cxv = constraint vertical, etc doesn't help anyone much.) Is there any nice listing of what makes a view hierarchy unprepared, or conventions that I can study and follow? Thx - Kevin
Try moving all of your code out of viewDidLoad and into viewWillAppear. The view hierarchy is not fully established in viewDidLoad. - Michael

1 Answers

0
votes

I did the whole piece of code over again, from scratch, and it worked. Go figure. Here is the code that worked properly. As far as I can see, it is pretty much the same code, with the exception of a minor name change here and there.

I have no idea why this version works, and the previous version did not. It makes me wonder if something was different in the Interface Builder setup.

class ViewController: UIViewController {

@IBOutlet weak var viewbb: UIView!
@IBOutlet weak var myLabel: UILabel!
@IBOutlet weak var centerConstraint: NSLayoutConstraint!
@IBOutlet weak var myButton: UIButton!

@IBOutlet weak var label2: UILabel!
@IBOutlet weak var label2centerConstraint: NSLayoutConstraint!
@IBOutlet weak var label2Yconstraint: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // remove the IB centering constraint, then add our own between label and button
    viewbb.removeConstraint(centerConstraint)

    // make a new constraint to center label over button
    let newcon = NSLayoutConstraint(item: myLabel,
        attribute: NSLayoutAttribute.CenterX,
        relatedBy: NSLayoutRelation.Equal,
        toItem: myButton,
        attribute: NSLayoutAttribute.CenterX,
        multiplier: 1.0,
        constant: 0)
    self.view.addConstraint(newcon)

    // remove the IB centering constraint, then add our own between label and button
    viewbb.removeConstraint(label2centerConstraint)
    viewbb.removeConstraint(label2Yconstraint)

    // make a new constraint to center label over button
    let newcon2 = NSLayoutConstraint(item: label2,
        attribute: NSLayoutAttribute.CenterX,
        relatedBy: NSLayoutRelation.Equal,
        toItem: myLabel,
        attribute: NSLayoutAttribute.CenterX,
        multiplier: 1.0,
        constant: -100) // left of mylabel

    // make a new constraint to center label over button
    let newcon3 = NSLayoutConstraint(item: label2,
        attribute: NSLayoutAttribute.CenterY,
        relatedBy: NSLayoutRelation.Equal,
        toItem: myLabel,
        attribute: NSLayoutAttribute.CenterY,
        multiplier: 1.0,
        constant: 0) // left of mylabel

    self.view.addConstraint(newcon3)
    self.view.addConstraint(newcon2)
}