4

I have an issue with auto layout constraints for UIView inside the UIStackView.

My goal is to create a view:

private static func makeSpace(with value: CGFloat) -> UIView {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = true

    let heightConstraint =  view.heightAnchor.constraint(lessThanOrEqualToConstant: value)
    heightConstraint.priority = UILayoutPriorityDefaultHigh

    NSLayoutConstraint.activate([
        view.widthAnchor.constraint(equalToConstant: 295),
        heightConstraint
    ])

    return view
}

I want to add this as an arranged subview to UIStackView as a space between other UIStackView arranged subviews.

When I inspect these views by using visual inspector, my space view has height 0. Although, when I change constraint to equalToConstant, height is calculated properly.

I want to use lessThanOrEqualToConstant to allow these spaces to shrink in case the screen layout is too small to fit them in proper size.

Has anyone else faced this problem?

Daumantas Versockas
  • 797
  • 1
  • 10
  • 29
  • aren't you supposed to enter some value in that field ? (CGFloat I guess?) – Ashish Bahl Dec 13 '17 at 09:59
  • What do you mean? I am passing a `value: CGFloat` to constraint... – Daumantas Versockas Dec 13 '17 at 10:02
  • The answer depends on the exact behaviour you are trying to achieve, but you can try setting the `verticalCompressionResistance` of your view to 1000. This should make your spacing view shrink only when needed. You can also make the heightConstraint required instead of only high priority. – David Ganster Dec 13 '17 at 10:09
  • lessThanOrEqualToConstant constraint is not sufficient to make autolayout know the acutal height but will work only if the view is hooked top and bottom to a resizable element like UILabel – Shehata Gamal Dec 13 '17 at 10:16
  • @DavidGanster, vertical compression resistance doesn't work. Actually, I don't see why it should, because, it is used for views with intrinsic size. – Daumantas Versockas Dec 13 '17 at 10:59

3 Answers3

0

See this error in layout happing with less than Constaint enter image description here

but with top and bottom resizable like UILabel it works enter image description here

Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • But for me it doesn't make sense. At first, I have a collection of views, which I want to separate with spaces. Each space is an arranged subview in `UIStackView`. Secondly, inside the stack view, any view shouldn't need to know anything about other arranged subviews. And at last, My issue is runtime and it doesn't generate any auto layout errors in console. – Daumantas Versockas Dec 13 '17 at 11:02
  • Why not try to enumerate constraints in run time and change the constant as you like according to a frame calculation – Shehata Gamal Dec 13 '17 at 11:16
  • It is one of the options, I agree. Although, this seems like something what AutoLayout should handle... – Daumantas Versockas Dec 13 '17 at 11:18
0

The code works, because 0 is in fact less than or equal to 295. If you want it 295 or less depending on other views in stack, you need another constraint telling it to expand to 295 if it is possible:

private static func makeSpace(with value: CGFloat) -> UIView {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = true

    // this keeps it under the value, we want this to be enforced, so priority stays `required`
    let heightConstraint1 =  view.heightAnchor.constraint(lessThanOrEqualToConstant: value)

    // this constraint will drive the size (the other one is just correcting it)
    let heightConstraint2 =  view.heightAnchor.constraint(equalToConstant: value)
    // modify this priority according to compression resistance of the other views
    heightConstraint2.priority = UILayoutPriority(rawValue: 240)

    NSLayoutConstraint.activate([
        view.widthAnchor.constraint(equalToConstant: 295),
        heightConstraint1,
        heightConstraint2
    ])

    return view
}

The first constraint keeps the space under the value constant (e.g. 295). This constraint has to have required priority, because you don't want to extend this height by any means.

The second constraint tells it to keep the height equal to value. But it has a lower priority. So other constraints can override it. If that happens, the autolayout system will try to get as close to fulfilling it as possible - so if it cannot be 295, but it can be 230, that will happen.

Side note:

If you don't care about expanding it to bigger value than value (in example 295), then you don't need the first constraint at all. I kept it there because I assume that is the limit for the space that has to be kept.

Another side note:

CompressionResistancePriority and ContentHuggingPriority are meant for views with intrinsic size, which does not apply to a UIView.

Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
  • Thank you for your answer. I have tested your explanation. Currently views are created with non-zero height. Although, when when screen layout is smaller (for example iPhone SE), views are not getting smaller and contains the same size as before. – Daumantas Versockas Dec 13 '17 at 11:17
  • try using lower priority for the height constraint, e.g.: `heightConstraint2.priority = UILayoutPriority(rawValue: 140)` – Milan Nosáľ Dec 13 '17 at 11:35
  • 2
    I have just checked your solution It did help! Thank you! – Daumantas Versockas Dec 14 '17 at 06:52
0
**//1** 

 let child = UIView()
  child.translatesAutoresizingMaskIntoConstraints = false
  child.backgroundColor = .red
  view.addSubview(child)


**//2**

  child.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
  child.bottomAnchor.constraint(equalTo:     view.safeAreaLayoutGuide.bottomAnchor).isActive = true
  child.widthAnchor.constraint(equalToConstant: 128).isActive = true
  child.centerXAnchor.constraint(equalTo:   view.safeAreaLayoutGuide.centerXAnchor).isActive = true
yassine menssi
  • 325
  • 5
  • 6