如果显示 1 个标签,则自动布局不起作用,但适用于 2 个标签

Auto-layout is not working in case 1 label is shown, but works with 2 labels

当标签是容器视图层次结构中的唯一标签时,标签会扩展到其容器之外。如果有 2 个标签,它工作正常并且两个标签都在容器视图中。

我的实际用例更复杂,但我尝试将其简化为以下代码。

Xcode 10.2 游乐场代码 (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))


结果如下图所示:

如果我尝试使用 2 个标签,它将产生正确的结果,这样标签就不会扩展其容器的宽度:

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))

结果如下图所示:

我希望当我只显示 1 个标签时,标签不会扩展到其容器之外。

我想知道为什么前导和尾随约束在第一种情况下(有 1 个标签)不能正常工作,但在第二种情况下(有 2 个标签)却能正常工作?

我发现你在设置 label1.setContentHuggingPriority(.required, for: .horizontal)。请尝试将优先级降低到 high 而不是 require

你有一些地方出了问题。

使用您的 "two label" playground 页面 as-is,如果您将 label2 的文本更改为:

label2.text = "01234567890 ABCDEFGHIJ"

你会得到这个:

如您所见,有两个标签并不 "solve the problem" ...它只是看起来有,因为您为第二个标签使用了非常短的字符串。

发生的事情是:当 auto-layout 处理约束并发现它不能完全满足所有约束时,它会使用优先级来确定它可以打破哪些约束而不会引发错误。在您的情况下,您正在设置各种约束和优先级,这些约束和优先级 不正确 授予 auto-layout 更改布局的权限。

所以,首先,我不知道您是从哪里想到将 topologyView 前导、尾随、顶部和底部约束的优先级设置为 .defaultLow 的。这样做是 显式地 告诉 auto-layout 在需要时忽略这些约束。然后,您添加一个比 topologyView 更宽的标签,并且 auto-layout 遵循您的指示并打破约束。

在您的 "two label" 示例中,auto-layout 优先于 label2 -- 这只是 发生 以使其看起来正确(直到你把标签加宽了)。

接下来,您使用 greaterThanOrEqualTo 不正确。通过设置:

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

你是说“让 topologyView 的后缘 超越 containerView 的后缘 。你真正想要的是 小于:

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

这将使 topologyView 的后缘小于 containerView 的后缘。

现在,不清楚您是否希望标签适合 containerView 的整个宽度,或者如果它们都很短,您是否希望它们居中。

如果全宽,使用 equalTo...如果居中,使用 greatThanOrEqualTo 前导,lessThanOrEqualTo 尾随。

同样的事情也适用于底部约束。

所以...您的 "single label" 示例变为:

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()

导致(短标签):

或(长标签):

而您的 "two label" 示例是:

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()

短标签和长标签结果: