-2

The label gets expanded outside of its container when it's the only within the container's view hierarchy. In case there are 2 labels, it works fine and both labels are staying within the container view.

My real use case is more complicated but I tried to simplify it to the code below.

Xcode 10.2 playground code (Swift 5):

import UIKit
import Foundation
import PlaygroundSupport

let viewController = UIViewController()
viewController.view.backgroundColor = UIColor.green

let containerView = UIView()
containerView.backgroundColor = .gray
containerView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(containerView)

containerView.widthAnchor.constraint(equalToConstant: 200).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
containerView.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor).isActive = true

let topologyView = UIView()
topologyView.backgroundColor = .blue
topologyView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(topologyView)

let leadingConstraint = topologyView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor)
print(leadingConstraint.priority)
leadingConstraint.priority = .defaultLow
leadingConstraint.isActive = true

let trailingConstraint = topologyView.trailingAnchor.constraint(greaterThanOrEqualTo: containerView.trailingAnchor)
trailingConstraint.priority = .defaultLow
trailingConstraint.isActive = true

let topConstraint = topologyView.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor)
topConstraint.priority = .defaultLow
topConstraint.isActive = true

let bottomConstraint = topologyView.bottomAnchor.constraint(greaterThanOrEqualTo: containerView.bottomAnchor)
bottomConstraint.priority = .defaultLow
bottomConstraint.isActive = true

topologyView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
topologyView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true

let label1Title = "1234"
let label1 = UILabel()
label1.translatesAutoresizingMaskIntoConstraints = false
label1.text = Array(repeating: label1Title, count: 10).joined()
label1.setContentHuggingPriority(.required, for: .horizontal)
label1.setContentCompressionResistancePriority(.required, for: .horizontal)
topologyView.addSubview(label1)

label1.leadingAnchor.constraint(equalTo: topologyView.leadingAnchor).isActive = true
label1.trailingAnchor.constraint(equalTo: topologyView.trailingAnchor).isActive = true
label1.bottomAnchor.constraint(equalTo: topologyView.bottomAnchor).isActive = true
label1.topAnchor.constraint(equalTo: topologyView.topAnchor).isActive = true

let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 350))
window.rootViewController = viewController

PlaygroundPage.current.liveView = window
PlaygroundPage.current.needsIndefiniteExecution = true

window.makeKeyAndVisible()

print(topologyView.contentHuggingPriority(for: .horizontal))
print(topologyView.contentCompressionResistancePriority(for: .horizontal))

print(containerView.contentHuggingPriority(for: .horizontal))
print(containerView.contentCompressionResistancePriority(for: .horizontal))


The result is shown in the picture below: a busy cat

In case if I try to have 2 labels instead, it will produce the correct result so that labels won't expand it's container's width:

import UIKit
import Foundation
import PlaygroundSupport

let viewController = UIViewController()
viewController.view.backgroundColor = UIColor.green

let containerView = UIView()
containerView.backgroundColor = .gray
containerView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(containerView)

containerView.widthAnchor.constraint(equalToConstant: 200).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
containerView.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor).isActive = true

let topologyView = UIView()
topologyView.backgroundColor = .blue
topologyView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(topologyView)

let leadingConstraint = topologyView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor)
print(leadingConstraint.priority)
leadingConstraint.priority = .defaultLow
leadingConstraint.isActive = true

let trailingConstraint = topologyView.trailingAnchor.constraint(greaterThanOrEqualTo: containerView.trailingAnchor)
trailingConstraint.priority = .defaultLow
trailingConstraint.isActive = true

let topConstraint = topologyView.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor)
topConstraint.priority = .defaultLow
topConstraint.isActive = true

let bottomConstraint = topologyView.bottomAnchor.constraint(greaterThanOrEqualTo: containerView.bottomAnchor)
bottomConstraint.priority = .defaultLow
bottomConstraint.isActive = true

topologyView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
topologyView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true

let label1Title = "1234"
let label1 = UILabel()
label1.translatesAutoresizingMaskIntoConstraints = false
label1.text = Array(repeating: label1Title, count: 10).joined()
label1.setContentHuggingPriority(.required, for: .horizontal)
label1.setContentCompressionResistancePriority(.required, for: .horizontal)
topologyView.addSubview(label1)

label1.leadingAnchor.constraint(equalTo: topologyView.leadingAnchor).isActive = true
label1.trailingAnchor.constraint(equalTo: topologyView.trailingAnchor).isActive = true
label1.topAnchor.constraint(equalTo: topologyView.topAnchor).isActive = true

let label2 = UILabel()
label2.translatesAutoresizingMaskIntoConstraints = false
label2.text = "123124128"
label2.setContentHuggingPriority(.required, for: .horizontal)
label2.setContentCompressionResistancePriority(.required, for: .horizontal)
topologyView.addSubview(label2)

label2.leadingAnchor.constraint(equalTo: topologyView.leadingAnchor).isActive = true
label2.trailingAnchor.constraint(equalTo: topologyView.trailingAnchor).isActive = true
label2.bottomAnchor.constraint(equalTo: topologyView.bottomAnchor).isActive = true
label2.topAnchor.constraint(equalTo: label1.bottomAnchor).isActive = true

let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 350))
window.rootViewController = viewController

PlaygroundPage.current.liveView = window
PlaygroundPage.current.needsIndefiniteExecution = true

window.makeKeyAndVisible()

print(topologyView.contentHuggingPriority(for: .horizontal))
print(topologyView.contentCompressionResistancePriority(for: .horizontal))

print(containerView.contentHuggingPriority(for: .horizontal))
print(containerView.contentCompressionResistancePriority(for: .horizontal))

The result is shown in the picture below: a busy cat

I expect that when I display just 1 label, the label wouldn't expand outside of its container.

I wonder why leading and trailing constraints are not working properly in the first case (with 1 label), but do work in the second case (with 2 labels)?

  • I found you setting `label1.setContentHuggingPriority(.required, for: .horizontal)`. Please try decrease priority to `high` instead `require` – Thanh Vu Jul 05 '19 at 05:06

2 Answers2

1

You have a couple of things going wrong.

Using your "two label" playground page as-is, if you change the text of label2 to:

label2.text = "01234567890 ABCDEFGHIJ"

You will get this:

enter image description here

As you see, having two labels doesn't "solve the problem" ... it just appeared to, because you used a very short string for the second label.

What's happening is: When auto-layout processes the constraints and finds it cannot fully satisfy all of them, it uses the priorities to determine which ones it can break without throwing an error. In your case, you're setting various constraints and priorities that are incorrectly giving auto-layout permission to change the layout.

So, first, I don't know where you got the idea to set the priority for topologyView leading, trailing, top and bottom constraints to .defaultLow. What that does is explicitly tells auto-layout to ignore those constraints if it needs to. You then add a label which will be wider than topologyView, and auto-layout follows your instructions and breaks the constraints.

In your "two label" example, auto-layout is giving priority to label2 -- which just happened to make it look correct (until you make the label wider).

Next, you're using greaterThanOrEqualTo incorrectly. By setting:

topologyView.trailingAnchor.constraint(greaterThanOrEqualTo: containerView.trailingAnchor)

you're saying "let the trailing edge of topologyView go beyond the trailing edge of containerView. What you really want is less than:

topologyView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor)

That will keep the trailing edge of topologyView less than the trailing edge of containerView.

Now, it's not clear if you want the labels to fit the full width of containerView, or if you want them centered if they are both short.

If the full width, use equalTo... if centered, use greatThanOrEqualTo leading, and lessThanOrEqualTo trailing.

The same thing applies to the bottom constraint.

So... your "single label" example becomes:

import UIKit
import Foundation
import PlaygroundSupport

let viewController = UIViewController()
viewController.view.backgroundColor = UIColor.green

let containerView = UIView()
containerView.backgroundColor = .gray
containerView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(containerView)

containerView.widthAnchor.constraint(equalToConstant: 200).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
containerView.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor).isActive = true

let topologyView = UIView()
topologyView.backgroundColor = .blue
topologyView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(topologyView)

let leadingConstraint = topologyView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor)
// use default priority
//leadingConstraint.priority = .defaultLow
leadingConstraint.isActive = true

// use lessThanOrEqualTo
let trailingConstraint = topologyView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor)
// use default priority
//trailingConstraint.priority = .defaultLow
trailingConstraint.isActive = true

let topConstraint = topologyView.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor)
// use default priority
//topConstraint.priority = .defaultLow
topConstraint.isActive = true

// use lessThanOrEqualTo
let bottomConstraint = topologyView.bottomAnchor.constraint(lessThanOrEqualTo: containerView.bottomAnchor)
// use default priority
//bottomConstraint.priority = .defaultLow
bottomConstraint.isActive = true

topologyView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
topologyView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true

let label1Title = "1234"
let label1 = UILabel()
label1.backgroundColor = .yellow
label1.translatesAutoresizingMaskIntoConstraints = false
label1.text = Array(repeating: label1Title, count: 10).joined()
// we can leave Hugging and Compression at default Priority
//label1.setContentHuggingPriority(.required, for: .horizontal)
//label1.setContentCompressionResistancePriority(.required, for: .horizontal)
topologyView.addSubview(label1)

label1.leadingAnchor.constraint(equalTo: topologyView.leadingAnchor).isActive = true
label1.trailingAnchor.constraint(equalTo: topologyView.trailingAnchor).isActive = true
label1.bottomAnchor.constraint(equalTo: topologyView.bottomAnchor).isActive = true
label1.topAnchor.constraint(equalTo: topologyView.topAnchor).isActive = true

let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 350))
window.rootViewController = viewController

PlaygroundPage.current.liveView = window
PlaygroundPage.current.needsIndefiniteExecution = true

window.makeKeyAndVisible()

Resulting in (short label):

enter image description here

or (long label):

enter image description here

And your "two label" example is:

import UIKit
import Foundation
import PlaygroundSupport

let viewController = UIViewController()
viewController.view.backgroundColor = UIColor.green

let containerView = UIView()
containerView.backgroundColor = .gray
containerView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(containerView)

containerView.widthAnchor.constraint(equalToConstant: 200).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
containerView.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor).isActive = true

let topologyView = UIView()
topologyView.backgroundColor = .blue
topologyView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(topologyView)

let leadingConstraint = topologyView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor)
// use default priority
//leadingConstraint.priority = .defaultLow
leadingConstraint.isActive = true

// use lessThanOrEqualTo
let trailingConstraint = topologyView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor)
// use default priority
//trailingConstraint.priority = .defaultLow
trailingConstraint.isActive = true

let topConstraint = topologyView.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor)
// use default priority
//topConstraint.priority = .defaultLow
topConstraint.isActive = true

// use lessThanOrEqualTo
let bottomConstraint = topologyView.bottomAnchor.constraint(lessThanOrEqualTo: containerView.bottomAnchor)
// use default priority
//bottomConstraint.priority = .defaultLow
bottomConstraint.isActive = true

topologyView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
topologyView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true

let label1Title = "1234"
let label1 = UILabel()
label1.backgroundColor = .yellow
label1.translatesAutoresizingMaskIntoConstraints = false
label1.text = Array(repeating: label1Title, count: 10).joined()
// we can leave Hugging and Compression at default Priority
//label1.setContentHuggingPriority(.required, for: .horizontal)
//label1.setContentCompressionResistancePriority(.required, for: .horizontal)
topologyView.addSubview(label1)

label1.leadingAnchor.constraint(equalTo: topologyView.leadingAnchor).isActive = true
label1.trailingAnchor.constraint(equalTo: topologyView.trailingAnchor).isActive = true
label1.topAnchor.constraint(equalTo: topologyView.topAnchor).isActive = true

let label2 = UILabel()
label2.backgroundColor = .orange
label2.translatesAutoresizingMaskIntoConstraints = false
label2.text = "012345 ABCDE"
// we can leave Hugging and Compression at default Priority
//label2.setContentHuggingPriority(.required, for: .horizontal)
//label2.setContentCompressionResistancePriority(.required, for: .horizontal)
topologyView.addSubview(label2)

label2.leadingAnchor.constraint(equalTo: topologyView.leadingAnchor).isActive = true
label2.trailingAnchor.constraint(equalTo: topologyView.trailingAnchor).isActive = true
label2.bottomAnchor.constraint(equalTo: topologyView.bottomAnchor).isActive = true
label2.topAnchor.constraint(equalTo: label1.bottomAnchor).isActive = true

let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 350))
window.rootViewController = viewController

PlaygroundPage.current.liveView = window
PlaygroundPage.current.needsIndefiniteExecution = true

window.makeKeyAndVisible()

with short and long label results:

enter image description here

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
0

I found you setting label1.setContentHuggingPriority(.required, for: .horizontal). Please try decrease priority to high instead require.

Thanh Vu
  • 1,599
  • 10
  • 14
  • Hi! Thanks for your response. Constraints created through anchors don't require to be manually added to the parent. They just need to be activated. Please, check the document for a reference: https://developer.apple.com/documentation/uikit/nslayoutanchor – Leonid Kokhnovych Jul 05 '19 at 04:57
  • @LeonidKokhnovych Oh, There is my confusion. So sorry – Thanh Vu Jul 05 '19 at 05:03
  • @LeonidKokhnovych Sorry about my confusion. I just update my answer. Please tell me if it work. I think your problem is constraint priority. – Thanh Vu Jul 05 '19 at 07:08
  • Thanks for your response! I have tried changing ```label1.setContentHuggingPriority(.required, for: .horizontal)``` to ```label1.setContentHuggingPriority(.defaultHigh, for: .horizontal)``` and it doesn't change anything. For me, it looks more like a bug in UIKit or I'm missing something important. – Leonid Kokhnovych Jul 05 '19 at 21:15