Cartography constraints with TopLayoutGuide

1.9k views Asked by At

I have a view which I want to look like this:

|---------------|
|               | <- navBar
|---------------|
|               | <- topView
|---------------|
|               |
|               |
|               |
|---------------|

Everything I want is to stick topView.top to navBar.bottom. I've decided to go with Cartography and implemented following code (trying to stick to MVC ofc):

In my UIViewController subclass:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    aView?.topLayoutGuide = self.topLayoutGuide // where aView is my subclass of UIView, inserted in loadView method
}

In my UIView subclass:

var topLayoutGuide: UILayoutSupport?

override func updateConstraints() {
    var constraint = NSLayoutConstraint?()
    layout(topView) { (topView) in
        constraint = topView.top == topView.superview!.top
    }
    topView.superview!.removeConstraint(constraint!)

    layout(topView) { topView in
        topView.top == topView.superview!.top + (self.topLayoutGuide?.length ?? 0)
        topView.height == 67
        topView.left == topView.superview!.left
        topView.width == topView.superview!.width
    }

    super.updateConstraints()
}

The problem is that I receive following logs and I have no idea how to fix it:

Unable to simultaneously satisfy constraints.
[...]
(
    "<NSLayoutConstraint:0x7fe6a64e4800 V:|-(64)-[UIView:0x7fe6a6505d80]   (Names: '|':MyApp.MyView:0x7fe6a360d4c0 )>",
    "<NSLayoutConstraint:0x7fe6a3538a80 V:|-(0)-[UIView:0x7fe6a6505d80]   (Names: '|':MyApp.MyView:0x7fe6a360d4c0 )>"
)

Seems I need some help. How to do it properly? I don't want to implement constraints in UIViewController and I don't want to use Storyboards.

Thanks for any help!

2

There are 2 answers

2
bryanjclark On BEST ANSWER

One solution: create a reference to that particular NSLayoutConstraint, and update its constant:

var topLayoutConstraint: NSLayoutConstraint!

override func updateConstraints() { 
    layout(topView) { topView in
        topLayoutConstraint = topView.top == topView.superview!.top
        topView.height == 67
        topView.left == topView.superview!.left
        topView.width == topView.superview!.width
    }

    super.updateConstraints()
}

... then later, you can adjust the constant of that NSLayoutConstraint like so:

topLayoutConstraint.constant = 50

The only question that's left is - when do you update the topLayoutConstraint? One solution is to create a protocol, MyViewInterface:

protocol MyViewInterface: class {
    var topLayoutGuideLength: CGFloat { get set }
}

Then, in our UIView, when that topLayoutGuideLength property is changed, we adjust the constant of our NSLayoutConstraint:

public class MyView: UIView, MyViewInterface {
    public var topLayoutGuideLength: CGFloat = 0 {
        didSet {
            topLayoutConstraint.constant = topLayoutGuideLength
        }
    }
}

Finally, in our UIViewController (where our topLayoutGuide lives):

public class myViewController: UIViewController {

    private var myView: MyView

    // ...

    public override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        myView.topLayoutGuideLength = topLayoutGuide.length
    }
}

The end result? Whenever viewDidLayoutSubviews() is triggered on our UIViewController, our myView's topLayoutGuideLength property is updated, which triggers a change in that topLayoutConstraint's constant.

4
erenkabakci On

Another lack of documentation from cartography...

I came across topLayoutGuideCartography and bottomLayoutGuideCartography properties as a UIViewController extension in Cartography. Using them could give you the desired output without overriding viewDidLayoutSubviews

You can simply use this property to anchor your view's top.

override func updateConstraints() { 
    layout(topView) { topView in
       topView.top == topLayoutGuideCartography
       topView.height == 67
       topView.left == topView.superview!.left
       topView.width == topView.superview!.width
    }

    super.updateConstraints()
}