Vive Vive - 23 days ago 8
iOS Question

Cartography constraints with TopLayoutGuide

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!

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.