Help with first challenge

I first tried to copy the code from the second section of the chapter (“View Swapping”):

class NerdTabViewController: NSViewController {

    //var box = NSBox()
    var buttons: [NSButton] = []
    var contentView: NSView?

    func selectTabAtIndex(index: Int) {
        assert((0..<childViewControllers.count).contains(index), "index out of range")
        for (i, button) in buttons.enumerate() {
            button.state = (index == i) ? NSOnState : NSOffState
        }
        let viewController = childViewControllers[index]
        //box.contentView = viewController.view
        contentView?.removeFromSuperview()
        view.addSubview(viewController.view)
        contentView = viewController.view
    }

And copied the simple substitution into the “reset” method:

        //box.translatesAutoresizingMaskIntoConstraints = false
        //box.borderType = .NoBorder
        //box.boxType = .Custom
        contentView?.translatesAutoresizingMaskIntoConstraints = false

        let separator = NSBox()
        separator.boxType = .Separator
        separator.translatesAutoresizingMaskIntoConstraints = false

        view.subviews = [stackView, separator, /*box*/]

        var views = ["stack": stackView, "separator": separator, /*"box": box*/]
        let metrics = ["buttonHeight": buttonHeight]
        func addVisualFormatConstraints(visualFormat: String) {
            let constraints = NSLayoutConstraint.constraintsWithVisualFormat(visualFormat, options: [], metrics: metrics, views: views)
            NSLayoutConstraint.activateConstraints(constraints)
        }
        addVisualFormatConstraints("H:|[stack]|")
        addVisualFormatConstraints("H:|[separator]|")
        if let contentView = contentView {
            view.subviews.append(contentView)
            views["content"] = contentView
            addVisualFormatConstraints("H:|[content(>=100)]|")
            addVisualFormatConstraints("V:|[stack(buttonHeight)][separator(==1)][content(>=100)]|")
        } else {
            addVisualFormatConstraints("V:|[stack(buttonHeight)][separator(==1)]|")
        }
        //addVisualFormatConstraints("H:|[box(>=100)]|")
        //addVisualFormatConstraints("V:|[stack(buttonHeight)][separator(==1)][box(>=100)]|")

But when I ran the code, the main view deflated to zero size, leaving just the buttons. The main view is actually there; I see the bottom-left corner of the art drawing over the buttons!

I don’t grok what “need to resize the view with its container” (from the “View Swapping” section) means. I tried adding:

        let views = ["content": contentView!]
        let constraints = NSLayoutConstraint.constraintsWithVisualFormat("|[content]|", options: [], metrics: nil, views: views) + NSLayoutConstraint.constraintsWithVisualFormat("V:|[content]|", options: [], metrics: nil, views: views)
        NSLayoutConstraint.activateConstraints(constraints)

at the end of “selectTabAtIndex,” but the window collapsed to nothing (just the title bar) with a bunch of console errors regarding conflicts. So the constraints set up in the “reset” method are there, but why don’t they help inflate the content view and/or its container to the proper size?

I’ve been rethinking over the past few days. I’ve been looking at various AutoLayout questions on StackOverflow and at Apple’s docs. Maybe I was overthinking doing layout relations between views. After more attempts, I came up with an answer.

The current custom view goes below (in the regular layman up/down sense, not in/out the screen) the buttons and separator. I made the reset method handle the buttons and separator only:

        //box.translatesAutoresizingMaskIntoConstraints = false
        //box.borderType = .NoBorder
        //box.boxType = .Custom

        let separator = NSBox()
        separator.boxType = .Separator
        separator.translatesAutoresizingMaskIntoConstraints = false

        view.subviews = [stackView, separator, /*box*/]

        let views = ["stack": stackView, "separator": separator, /*"box": box*/]
        let metrics = ["buttonHeight": buttonHeight]
        func addVisualFormatConstraints(visualFormat: String) {
            let constraints = NSLayoutConstraint.constraintsWithVisualFormat(visualFormat, options: [], metrics: metrics, views: views)
            NSLayoutConstraint.activateConstraints(constraints)
        }
        addVisualFormatConstraints("H:|[stack]|")
        addVisualFormatConstraints("H:|[separator]|")
        addVisualFormatConstraints("V:|[stack(buttonHeight)][separator(==1)]")  // Used to have a "|" at the end
        //addVisualFormatConstraints("H:|[box(>=100)]|")
        //addVisualFormatConstraints("V:|[stack(buttonHeight)][separator(==1)][box(>=100)]|")
        buttonsView = stackView
        separatorView = separator

There are new “buttonsView” and “separatorView” properties, so I can reference them later while connecting the current custom view. That “Used to have a ‘|’ at the end” is very important; I think it was the key to my problems. Since the view was forced to bottom out with the separator, the custom view either popped above it (overlaying the buttons) or below it but out of view (negative coordinates). The “selectTabAtIndex” method sets the custom view and its restraints:

    //var box = NSBox()
    var buttons: [NSButton] = []
    var contentView: NSView?
    var buttonsView = NSStackView()  // NEW
    var separatorView = NSBox()  // NEW

    func selectTabAtIndex(index: Int) {
        assert((0..<childViewControllers.count).contains(index), "index out of range")
        for (i, button) in buttons.enumerate() {
            button.state = (index == i) ? NSOnState : NSOffState
        }
        let viewController = childViewControllers[index]
        //box.contentView = viewController.view
        contentView?.removeFromSuperview()
        contentView = viewController.view
        contentView?.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(contentView!)
        let views = ["content": contentView!, "separator": separatorView]
        let contentHorizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[content(>=100)]|", options: [], metrics: nil, views: views)
        let contentVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:[separator][content(>=100)]|", options: [], metrics: nil, views: views)
        NSLayoutConstraint.activateConstraints(contentHorizontalConstraints + contentVerticalConstraints)
    }