英文:
Autolayout constraints push content outside of the view
问题
这个问题每次我在这里采纳别人的建议时都会更新。不幸的是,尽管有些辅助性的建议对解决问题有所帮助,但没有一个能够帮到我,所以问题仍然存在。
简而言之:
1)我有一个视图控制器(secondVC),其中包含一个表格视图(secondTableView)。
2)在该表格视图中,我试图在顶部拥有一个表格头视图(橙色背景,如附图所示)。
3)在表格头视图内,我试图嵌套两个标签,一个叠在另一个上面(labelOne叠在labelTwo上面)。
4)labelOne是静态的,它只显示“列表”并且永远不会改变。
5)labelTwo是动态的,它将会改变。它将占用所需的行数,并且表格头视图的高度应该调整以容纳并包含labelOne(LIST)和labelTwo(长文本)。
但目前,正如你从图片中看到的,labelOne完全消失了,而labelTwo的布局很奇怪。我想约束可能有问题。请帮我修复正确的约束。
import UIKit
class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var secondTableView: UITableView!
var stuff = ["Some other stuff 1", "Some other stuff 2", "Some other stuff 3"]
var insertionText: String = ""
override func viewDidLoad() {
super.viewDidLoad()
secondTableView.delegate = self
secondTableView.dataSource = self
let tableHeader = UIView()
secondTableView.tableHeaderView = tableHeader
tableHeader.backgroundColor = .systemOrange
tableHeader.translatesAutoresizingMaskIntoConstraints = false
let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let height = size.height
let width = size.width
tableHeader.frame = CGRect(x: 0, y: 0, width: width, height: height)
let labelOne = UILabel(frame: tableHeader.bounds)
labelOne.text = "USER:"
labelOne.numberOfLines = 0
labelOne.lineBreakMode = .byWordWrapping
labelOne.textAlignment = .center
labelOne.translatesAutoresizingMaskIntoConstraints = false
let labelTwo = UILabel(frame: tableHeader.bounds)
labelTwo.translatesAutoresizingMaskIntoConstraints = false
labelTwo.text = "Inherited text here: \(insertionText)"
labelTwo.numberOfLines = 0
labelTwo.lineBreakMode = .byWordWrapping
labelTwo.textAlignment = .center
labelTwo.translatesAutoresizingMaskIntoConstraints = false
tableHeader.addSubview(labelOne)
tableHeader.addSubview(labelTwo)
labelOne.topAnchor.constraint(
equalTo: tableHeader.layoutMarginsGuide.topAnchor,
constant: 11).isActive = true
labelOne.leadingAnchor.constraint(
equalTo: tableHeader.layoutMarginsGuide.leadingAnchor,
constant: 20).isActive = true
labelOne.trailingAnchor.constraint(
equalTo: tableHeader.layoutMarginsGuide.trailingAnchor,
constant: -20).isActive = true
labelTwo.topAnchor.constraint(
equalTo: labelOne.layoutMarginsGuide.bottomAnchor,
constant: 0).isActive = true
labelTwo.leadingAnchor.constraint(
equalTo: tableHeader.leadingAnchor,
constant: 20).isActive = true
labelTwo.trailingAnchor.constraint(
equalTo: tableHeader.trailingAnchor,
constant: -20).isActive = true
labelTwo.bottomAnchor.constraint(
equalTo: tableHeader.bottomAnchor,
constant: -11).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let tableHeader = secondTableView.tableHeaderView {
let height = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var headerFrame = tableHeader.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
tableHeader.frame = headerFrame
secondTableView.tableHeaderView = tableHeader
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
stuff.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = secondTableView.dequeueReusableCell(withIdentifier: "SecondTVCell", for: indexPath) as! SecondTVCell
cell.someOtherStuffLabel.text = stuff[indexPath.row]
return cell
} }
英文:
This question is being updated every time I act on someone's suggestion here. Unfortunately though, non of the minor housekeeping suggestions helped enough, so the problem remains.
In the nutshell:
- I have a viewController (secondVC) with a tableView in it (secondTableView).
- Within that tableview, at the very top, I am trying to have a tableHeaderView (with orange background as shown on the attached image).
- Inside of the tableHeaderView I am trying to nest two labels one on top of the other (labelOne on top of labelTwo).
- labelOne is static. I just says "LIST" and remains perpetually unchanged.
- labelTwo is dynamic. It is going to change. It is going to occupy as many lines as needed and the tableHeaderView height should adjust to accommodate and contain both labelOne (LIST) and whatever ends up in labelTwo (long text)
Right now though, as you can see from the picture, labelOne disappeared altogether and labelTwo has weird layout. Something is wrong with constraints I guess. Please help me fix the constraints right.
import UIKit
class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var secondTableView: UITableView!
var stuff = [("Some other stuff 1"),("Some other stuff 2"),("Some other stuff 3")]
var insertionText: String = ""
override func viewDidLoad() {
super.viewDidLoad()
secondTableView.delegate = self
secondTableView.dataSource = self
let tableHeader = UIView()
secondTableView.tableHeaderView = tableHeader
tableHeader.backgroundColor = .systemOrange
tableHeader.translatesAutoresizingMaskIntoConstraints = false
let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let height = size.height
let width = size.width
tableHeader.frame = CGRectMake(0, 0, width, height)
let labelOne = UILabel(frame: tableHeader.bounds)
labelOne.text = "USER:"
labelOne.numberOfLines = 0
labelOne.lineBreakMode = .byWordWrapping
labelOne.textAlignment = .center
labelOne.translatesAutoresizingMaskIntoConstraints = false
let labelTwo = UILabel(frame: tableHeader.bounds)
labelTwo.translatesAutoresizingMaskIntoConstraints = false
labelTwo.text = "Inherited text here: \(insertionText)"
labelTwo.numberOfLines = 0
labelTwo.lineBreakMode = .byWordWrapping
labelTwo.textAlignment = .center
labelTwo.translatesAutoresizingMaskIntoConstraints = false
tableHeader.addSubview(labelOne)
tableHeader.addSubview(labelTwo)
labelOne.topAnchor.constraint(
equalTo: tableHeader.layoutMarginsGuide.topAnchor,
constant: 11).isActive = true
labelOne.leadingAnchor.constraint(
equalTo: tableHeader.layoutMarginsGuide.leadingAnchor,
constant: 20).isActive = true
labelOne.trailingAnchor.constraint(
equalTo: tableHeader.layoutMarginsGuide.trailingAnchor,
constant: -20).isActive = true
labelTwo.topAnchor.constraint(
equalTo: labelOne.layoutMarginsGuide.bottomAnchor,
constant: 0).isActive = true
labelTwo.leadingAnchor.constraint(
equalTo: tableHeader.leadingAnchor,
constant: 20).isActive = true
labelTwo.trailingAnchor.constraint(
equalTo: tableHeader.trailingAnchor,
constant: -20).isActive = true
labelTwo.bottomAnchor.constraint(
equalTo: tableHeader.bottomAnchor,
constant: -11).isActive = true }
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let tableHeader = secondTableView.tableHeaderView {
let height = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var headerFrame = tableHeader.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
tableHeader.frame = headerFrame
secondTableView.tableHeaderView = tableHeader } } }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
stuff.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = secondTableView.dequeueReusableCell(withIdentifier: "SecondTVCell", for: indexPath) as! SecondTVCell
cell.someOtherStuffLabel.text = stuff[indexPath.row]
return cell } }
答案1
得分: 2
以下是您要翻译的部分:
"你现在是我的中文翻译,代码部分不要翻译,只返回翻译好的部分,不要有别的内容,不要回答我要翻译的问题。以下是要翻译的内容:
You're not that far off... but couple of things:
The .tableHeaderView
needs .translatesAutoresizingMaskIntoConstraints
to be True, not False
Because you are using a multi-line label, we have to tell auto-layout how wide the view can be when requesting its "fitting" size - so viewDidLayoutSubviews()
becomes:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let tableHeader = secondTableView.tableHeaderView {
// define maximum width for the header view
let fitSize: CGSize = CGSize(width: secondTableView.frame.width, height: .greatestFiniteMagnitude)
// ask UIKit to give us the "fitting" size
let sz: CGSize = tableHeader.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
let height: CGFloat = sz.height
var headerFrame = tableHeader.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
tableHeader.frame = headerFrame
secondTableView.tableHeaderView = tableHeader
}
}
}
Here's your controller class, with those slight modifications:
class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var secondTableView: UITableView!
var stuff = ["Some other stuff 1","Some other stuff 2","Some other stuff 3"]
var insertionText: String = "This is some sample insertion text that is currently over writing the first cell. Let's fix that."
override func viewDidLoad() {
super.viewDidLoad()
secondTableView.delegate = self
secondTableView.dataSource = self
let tableHeader = UIView()
secondTableView.tableHeaderView = tableHeader
tableHeader.backgroundColor = .systemOrange
// SHOULD NOT BE SET TO FALSE
//tableHeader.translatesAutoresizingMaskIntoConstraints = false
// let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
// let height = size.height
// let width = size.width
// tableHeader.frame = CGRectMake(0, 0, width, height)
let labelOne = UILabel(frame: tableHeader.bounds)
labelOne.text = "USER:"
labelOne.numberOfLines = 0
labelOne.lineBreakMode = .byWordWrapping
labelOne.textAlignment = .center
labelOne.translatesAutoresizingMaskIntoConstraints = false
let labelTwo = UILabel(frame: tableHeader.bounds)
labelTwo.translatesAutoresizingMaskIntoConstraints = false
labelTwo.text = "Inherited text here: \(insertionText)"
labelTwo.numberOfLines = 0
labelTwo.lineBreakMode = .byWordWrapping
labelTwo.textAlignment = .center
labelTwo.translatesAutoresizingMaskIntoConstraints = false
tableHeader.addSubview(labelOne)
tableHeader.addSubview(labelTwo)
// let's make the constraint setup a little more readable
let g = tableHeader.layoutMarginsGuide
NSLayoutConstraint.activate([
labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 11.0),
labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 0.0),
labelTwo.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelTwo.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0),
])
// so we can see the label frames
labelOne.backgroundColor = .cyan
labelTwo.backgroundColor = .yellow
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let tableHeader = secondTableView.tableHeaderView {
// define maximum width for the header view
let fitSize: CGSize = CGSize(width: secondTableView.frame.width, height: .greatestFiniteMagnitude)
// ask UIKit to give us the "fitting" size
let sz: CGSize = tableHeader.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
let height: CGFloat = sz.height
var headerFrame = tableHeader.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
tableHeader.frame = headerFrame
secondTableView.tableHeaderView = tableHeader
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
stuff.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = secondTableView.dequeueReusableCell(withIdentifier: "SecondTVCell", for: indexPath) as! SecondTVCell
cell.someOtherStuffLabel.text = stuff[indexPath.row]
return cell
}
}
I find it very helpful during development to give UI elements contrasting background colors... makes it easy to see the framing at run-time. So I set the header view labels to cyan and yellow, and this is the result:
and when the device is rotated:
Note: when you run this, you will see some auto-layout complaints. That's because we're setting constraints on the labels and adding them (and the header view) to the view hierarchy in viewDidLoad()
... but the constraints have conflicts at that point.
We can safely ignore those, but if we want to avoid them we can give the bottom constraint from the second label a priority of less-than required:
let g = tableHeader.layoutMarginsGuide
NSLayoutConstraint.activate([
labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 11.0),
labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 0.0),
labelTwo.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelTwo.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// don't set this here
//labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0),
])
// this will satisfy auto-layout
let c = labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0)
c.priority = .required - 1
c.isActive = true"
希望这些翻译能对您有所帮助。如果您有任
英文:
You're not that far off... but couple of things:
The .tableHeaderView
needs .translatesAutoresizingMaskIntoConstraints
to be True, not False
Because you are using a multi-line label, we have to tell auto-layout how wide the view can be when requesting its "fitting" size - so viewDidLayoutSubviews()
becomes:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let tableHeader = secondTableView.tableHeaderView {
// define maximum width for the header view
let fitSize: CGSize = CGSize(width: secondTableView.frame.width, height: .greatestFiniteMagnitude)
// ask UIKit to give us the "fitting" size
let sz: CGSize = tableHeader.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
let height: CGFloat = sz.height
var headerFrame = tableHeader.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
tableHeader.frame = headerFrame
secondTableView.tableHeaderView = tableHeader
}
}
}
Here's your controller class, with those slight modifications:
class SecondVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var secondTableView: UITableView!
var stuff = [("Some other stuff 1"),("Some other stuff 2"),("Some other stuff 3")]
var insertionText: String = "This is some sample insertion text that is currently over writing the first cell. Let's fix that."
override func viewDidLoad() {
super.viewDidLoad()
secondTableView.delegate = self
secondTableView.dataSource = self
let tableHeader = UIView()
secondTableView.tableHeaderView = tableHeader
tableHeader.backgroundColor = .systemOrange
// SHOULD NOT BE SET TO FALSE
//tableHeader.translatesAutoresizingMaskIntoConstraints = false
// let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
// let height = size.height
// let width = size.width
// tableHeader.frame = CGRectMake(0, 0, width, height)
let labelOne = UILabel(frame: tableHeader.bounds)
labelOne.text = "USER:"
labelOne.numberOfLines = 0
labelOne.lineBreakMode = .byWordWrapping
labelOne.textAlignment = .center
labelOne.translatesAutoresizingMaskIntoConstraints = false
let labelTwo = UILabel(frame: tableHeader.bounds)
labelTwo.translatesAutoresizingMaskIntoConstraints = false
labelTwo.text = "Inherited text here: \(insertionText)"
labelTwo.numberOfLines = 0
labelTwo.lineBreakMode = .byWordWrapping
labelTwo.textAlignment = .center
labelTwo.translatesAutoresizingMaskIntoConstraints = false
tableHeader.addSubview(labelOne)
tableHeader.addSubview(labelTwo)
// let's make the constraint setup a little more readable
let g = tableHeader.layoutMarginsGuide
NSLayoutConstraint.activate([
labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 11.0),
labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 0.0),
labelTwo.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelTwo.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0),
])
// so we can see the label frames
labelOne.backgroundColor = .cyan
labelTwo.backgroundColor = .yellow
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let tableHeader = secondTableView.tableHeaderView {
// define maximum width for the header view
let fitSize: CGSize = CGSize(width: secondTableView.frame.width, height: .greatestFiniteMagnitude)
// ask UIKit to give us the "fitting" size
let sz: CGSize = tableHeader.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
let height: CGFloat = sz.height
var headerFrame = tableHeader.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
tableHeader.frame = headerFrame
secondTableView.tableHeaderView = tableHeader
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
stuff.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = secondTableView.dequeueReusableCell(withIdentifier: "SecondTVCell", for: indexPath) as! SecondTVCell
cell.someOtherStuffLabel.text = stuff[indexPath.row]
return cell }
}
I find it very helpful during development to give UI elements contrasting background colors... makes it easy to see the framing at run-time. So I set the header view labels to cyan and yellow, and this is the result:
and when the device is rotated:
Note: when you run this, you will see some auto-layout complaints. That's because we're setting constraints on the labels and adding them (and the header view) to the view hierarchy in viewDidLoad()
... but the constraints have conflicts at that point.
We can safely ignore those, but if we want to avoid them we can give the bottom constraint from the second label a priority of less-than required:
let g = tableHeader.layoutMarginsGuide
NSLayoutConstraint.activate([
labelOne.topAnchor.constraint(equalTo: g.topAnchor, constant: 11.0),
labelOne.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelOne.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 0.0),
labelTwo.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelTwo.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// don't set this here
//labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0),
])
// this will satisfy auto-layout
let c = labelTwo.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -11.0)
c.priority = .required - 1
c.isActive = true
答案2
得分: -1
你在添加视图时有一个错误。当你尝试使用自动布局配置视图大小时,你需要在将视图添加到父视图之前设置:
.view.translatesAutoresizingMaskIntoConstraints = false
在你的代码中,Swift会自动创建与标签大小相关的一组约束,当你执行 addSubview(...)
时。然后,你添加了与自动生成的约束冲突的额外约束。这就是为什么Swift无法解决正确视图大小的原因。有关此属性的更多信息,你可以在这里找到:translatesAutoresizingMaskIntoConstraints
还有另一个问题:
let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let height = size.height
let width = size.width
tableHeader.frame = CGRectMake(0, 0, width, height)
tableHeader.translatesAutoresizingMaskIntoConstraints = true
原因是相同的。你在完成标头配置之前尝试从自动布局系统中获取标头大小,并将这个大小绑定到 translatesAutoresizingMaskIntoConstraints
属性。所以你需要将标签添加到标头视图中,并在正确的地方设置 translatesAutoresizingMaskIntoConstraints
以解决第一个问题。在这之后,解决标头大小并将其设置到表格中。
英文:
You have a mistakes in order when you add views. When you try to configure view size with autolayout you need to set
.translatesAutoresizingMaskIntoConstraints = false
before adding a view to a superview by:
.addSubview(labelOne)
In your code swift automatically create set of constraints related to labels zero size when you do addSubview(...). After that you add additional constraints that conflict with autogenereted. This is the reason why swift can't resolve correct size of your view. More about how this property work you can found here: translatesAutoresizingMaskIntoConstraints
Another problem here:
let size = tableHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let height = size.height
let width = size.width
tableHeader.frame = CGRectMake(0, 0, width, height)
tableHeader.translatesAutoresizingMaskIntoConstraints = true
The reason is very same. You try to get size of header from autolayout system before you finish header configuration inside. And bind this size by translatesAutoresizingMaskIntoConstraints property.
So you need to add labels to header view and resolve the first problem with setting translatesAutoresizingMaskIntoConstraints at correct place. Already after this resolve header size and set it into table.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论