2

XCode is throwing up the following constraint error when I attempt to use a UIStackView:

(
    "<NSAutoresizingMaskLayoutConstraint:0x7f87a1dfa360 h=--& v=--& V:[UIStackView:0x7f87a6403a00(0)]>",
    "<NSLayoutConstraint:0x7f87a6410590 'UISV-canvas-connection' UIStackView:0x7f87a6403a00.top == UIView:0x7f87a6409630.top>",
    "<NSLayoutConstraint:0x7f87a6444170 'UISV-canvas-connection' V:[UIView:0x7f87a644d790]-(0)-|   (Names: '|':UIStackView:0x7f87a6403a00 )>",
    "<NSLayoutConstraint:0x7f87a645bec0 'UISV-fill-equally' UIView:0x7f87a6407a10.height == UIView:0x7f87a6409630.height>",
    "<NSLayoutConstraint:0x7f87a6458f40 'UISV-fill-equally' UIView:0x7f87a644d790.height == UIView:0x7f87a6409630.height>",
    "<NSLayoutConstraint:0x7f87a64306d0 'UISV-spacing' V:[UIView:0x7f87a6409630]-(5)-[UIView:0x7f87a6407a10]>",
    "<NSLayoutConstraint:0x7f87a643bea0 'UISV-spacing' V:[UIView:0x7f87a6407a10]-(5)-[UIView:0x7f87a644d790]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f87a643bea0 'UISV-spacing' V:[UIView:0x7f87a6407a10]-(5)-[UIView:0x7f87a644d790]>

My view controller is as follows:

public class ExampleController: UIViewController {

    let v1 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100))

    let v2 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 300))

    let v3 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 200))

    let parent1 = UIStackView()


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

        v1.backgroundColor = .red
        v2.backgroundColor = .green
        v3.backgroundColor = .blue

        parent1.axis = .vertical
        parent1.distribution = .fillEqually
        parent1.spacing = 5 // TODO: This causes the error!

        parent1.addArrangedSubview(v1)
        parent1.addArrangedSubview(v2)
        parent1.addArrangedSubview(v3)

        view.addSubview(parent1)
    }

    // MARK: Constraints

    private var didUpdateConstraints = false

    override public func updateViewConstraints() {
        if !didUpdateConstraints {

            parent1.snp.makeConstraints { (make) -> Void in
                make.edges.equalToSuperview()
            }

            didUpdateConstraints = true
        }
        super.updateViewConstraints()
    }
}

The distribution of the stack view doesn't seem to make a difference. Whenever spacing is set, I receive the error.


  1. What is the conflict with my constraints?

  2. What is UISV-canvas-connection?

sdasdadas
  • 23,917
  • 20
  • 63
  • 148
  • `UISV-canvas-connection` it is `UIScrollView` constraint with top. – dahiya_boy Jan 20 '17 at 11:25
  • 1
    a great approach with stack view is ***only set the heights*** of the new items you are adding. allow the width/etc to be handled automatically by the enclosing stack view and scroll view. – Fattie Jan 20 '17 at 11:30
  • @sdasdadas Do you add constraints to `parent1` relative to `self.view`? – ricardopereira Jan 20 '17 at 11:34

3 Answers3

5

It's actually not breaking because of the spacing. It's breaking because of the way you are creating the views :D

When you create a view like...

let v1 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100))

When the view gets laid out it gets a default set of constraints added to it. The same is true for the UIStackView.

When you create it like...

let parent1 = UIStackView()

It gets a frame of (0, 0, 0, 0) and then constraints added to it to keep that frame position and height.

The error you are getting is because of the height and width of the stack view.

<NSAutoresizingMaskLayoutConstraint:0x7f87a1dfa360 h=--& v=--& V:[UIStackView:0x7f87a6403a00(0)]>

This line is referring to those "automatic" constraints.

Your best option is to recreate the views widths and height using constraints.

Like this...

// using this format means you can create the view and add all the
// other config to it at the same time
let v1: UIView = {
    let u = UIView()

    // this line stops the "automatic" constraints being added
    u.translatesAutoresizingMaskIntoConstraints = false

    u.backgroundColor = .red

    // now you have to add your own constraints though
    u.heightAnchor.constraint(equalToConstant: 100).isActive = true
    u.widthAnchor.constraint(equalToConstant: 250).isActive = true
    return u
}()

let v2: UIView = {
    let u = UIView()
    u.translatesAutoresizingMaskIntoConstraints = false
    u.backgroundColor = .green
    u.heightAnchor.constraint(equalToConstant: 100).isActive = true
    u.widthAnchor.constraint(equalToConstant: 250).isActive = true
    return u
}()

let v3: UIView = {
    let u = UIView()
    u.translatesAutoresizingMaskIntoConstraints = false
    u.backgroundColor = .blue
    u.heightAnchor.constraint(equalToConstant: 100).isActive = true
    u.widthAnchor.constraint(equalToConstant: 250).isActive = true
    return u
}()

let parent1: UIStackView = {
    let s = UIStackView()
    s.translatesAutoresizingMaskIntoConstraints = false
    s.spacing = 5
    s.axis = .vertical
    s.distirbution = .fillEqually
    return s
}()

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

    // I moved all the config stuff into the creation of each view...
    // No need to have them here.

    parent1.addArrangedSubview(v1)
    parent1.addArrangedSubview(v2)
    parent1.addArrangedSubview(v3)

    view.addSubview(parent1)
}

A word of warning here though.

You are using .fillEqually in the stack view. This will give each view heights equal to each other. This is going to conflict with the height constraint you added to them. Maybe you should remove the constraints from each view entirely and let the stack view do the layout.

Like this...

let v2: UIView = {
    let u = UIView()
    u.translatesAutoresizingMaskIntoConstraints = false
    u.backgroundColor = .green
    return u
}()
Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • Thank you! To go one step further, how would I prevent this issue if my views (v1, v2, v3) were actually labels? Since their intrinsic content size is calculated automatically (afaik), it's not possible to set manual constraints... and their text varies making it impossible to know their width in advance. – sdasdadas Jan 20 '17 at 11:31
  • @sdasdadas you can def set manual constraints on labels. It will just override the intrinsic content size without conflicts. Check out my word of warning at the end of the answer too. Don't OVER constrain views. If you're adding views like labels to a stack view just add them without constraints and only add constraints if they are required. – Fogmeister Jan 20 '17 at 11:33
  • I see and that makes sense, thank you. However, when I replace the lines `UIView(frame: ...)` with `UILabel()` I receive a similar error. – sdasdadas Jan 20 '17 at 11:35
  • @sdasdadas Did you add the `translatesAutoresizingMaskIntoConstraints = false` – Fogmeister Jan 20 '17 at 11:35
  • Yes, I add them in the `viewDidLoad()` method before adding to the stack view. One for each label. – sdasdadas Jan 20 '17 at 11:37
  • @sdasdadas And you added that same line to the stack view? – Fogmeister Jan 20 '17 at 11:37
  • Well, SnapKit should handle that I believe when I set my constraints to equal the edges of the superview. That's probably the issue though - I'll accept the answer because you've more than answered my initial question. Thanks again! – sdasdadas Jan 20 '17 at 11:38
  • It does seem to do it and the docs do say (although ambiguously) that SnapKit handles calling `translatesAutoresizingMaskIntoConstraints`. Maybe it has something to do with calling it after I `addArrangedSubview`? – sdasdadas Jan 20 '17 at 11:44
  • 1
    @sdasdadas maybe. Oh. But when is the function called to snapkit? After viewDidLoad the constraints will be rendered. That's the point at which the error will occur. – Fogmeister Jan 20 '17 at 11:45
  • You're right and that's the issue. `viewDidLoad` is called before my constraints are being set. – sdasdadas Jan 20 '17 at 11:49
4

I was facing the same issue. What did the trick for me was to set the spacing of the UIStackView to 0 before setting the constraints. After setting the constraints, I then set the spacing to the desired amount. This cleared up the conflicting constraints.

Jose Calles
  • 163
  • 9
3

I tested it in a Playground and it's working. You just need to set translatesAutoresizingMaskIntoConstraints to false and add constraints to the UIStackView.

import UIKit
import PlaygroundSupport

public class ExampleController: UIViewController {

    let v1 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 100))
    let v2 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 300))
    let v3 = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 200))

    let parent1 = UIStackView()

    public override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        v1.backgroundColor = .red
        v2.backgroundColor = .green
        v3.backgroundColor = .blue

        parent1.axis = .vertical
        parent1.distribution = .fillEqually
        parent1.spacing = 5 // Not causing error

        parent1.addArrangedSubview(v1)
        parent1.addArrangedSubview(v2)
        parent1.addArrangedSubview(v3)

        parent1.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(parent1)

        NSLayoutConstraint.activate([
            parent1.topAnchor.constraint(equalTo: view.topAnchor),
            parent1.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            parent1.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            parent1.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }

}

PlaygroundPage.current.liveView = ExampleController()

Result:

enter image description here

ricardopereira
  • 11,118
  • 5
  • 63
  • 81
  • The reason it is working here is because your view has no predetermined size. Just added the line to the stack view in the OP's case won't work as the view has a predetermined size. – Fogmeister Jan 20 '17 at 11:41