Swift - 相互重叠的垂直堆栈元素
Swift - vertical stack elements overlaying each other
我很难通过 UIStackView 获得预期的效果。这是我的设置:
带有文本字段的字段元素:
class NewEditableFuelSheetField: UIView {
var titleText: String?
var textFieldText: String?
init(titleText: String, textFieldText: String) {
super.init(frame: .zero)
self.titleText = titleText
self.textFieldText = textFieldText
self.addSubview(editableField)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var editableField: UIStackView = {
let title = UILabel()
title.text = self.titleText
let textField = UITextField()
textField.isEnabled = false
let stack = UIStackView(arrangedSubviews: [title, textField])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
editableField.heightAnchor.constraint(equalToConstant: 50)
])
}
}
具有固定值的字段:
class NewFixedFuelSheetField: UIView {
var title: String?
var detail: String?
init(title: String, detail: String) {
super.init(frame: .zero)
self.title = title
self.detail = detail
configureAutoLayout()
self.addSubview(fixedField)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var fixedField: UIStackView = {
let title = UILabel()
let detail = UILabel()
title.text = self.title
detail.text = self.detail
let stack = UIStackView(arrangedSubviews: [title, detail])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stack)
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
fixedField.heightAnchor.constraint(equalToConstant: 50)
])
}
}
Header 包含一堆不可编辑字段的视图:
class NewFuelSheetHeaderView: UIView {
// MARK: Init
override init(frame: CGRect) {
super.init(frame: .zero)
self.addSubview(fuelSheetHeaderStack)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Properties
// 'detail' text will be brought in from API in next ticket
private lazy var flightNumber: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Flight number", detail: "VS0101")
}()
private lazy var aircraftReg: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Aircraft reg", detail: "GAAAA")
}()
private lazy var date: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Date", detail: "01.01.21")
}()
private lazy var time: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Time", detail: "12:01")
}()
private let supplier: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Supplier", detail: "i6Staging, BAPCO")
}()
private let fuelGrade: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Fuel grade", detail: "Jet A")
}()
private let freezePoint: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Freeze point", detail: "-40")
}()
private let specificGravity: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Specific gravity", detail: "0.793")
}()
private lazy var fuelSheetHeaderFirstRow: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
flightNumber,
aircraftReg,
date,
time
])
stack.axis = .horizontal
stack.distribution = .fillEqually
return stack
}()
private lazy var fuelSheetHeaderSecondRow: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
supplier,
fuelGrade,
freezePoint,
specificGravity
])
stack.axis = .horizontal
stack.distribution = .fillEqually
return stack
}()
private lazy var fuelSheetHeaderStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [fuelSheetHeaderFirstRow, fuelSheetHeaderSecondRow])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
// MARK: Configuration
private func configureAutoLayout() {
NSLayoutConstraint.activate([
fuelSheetHeaderStack.topAnchor.constraint(equalTo: self.topAnchor, constant: 50),
fuelSheetHeaderStack.leftAnchor.constraint(equalTo: leftAnchor),
fuelSheetHeaderStack.rightAnchor.constraint(equalTo: rightAnchor),
fuelSheetHeaderStack.heightAnchor.constraint(equalToConstant: 200)
])
}
}
最终需要放置在 header 下方的第二个视图:
class NewFuelSheetRefuelInfoView: UIView {
// MARK: Init
override init(frame: CGRect) {
super.init(frame: .zero)
self.addSubview(refuelStackView)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Properties
private lazy var preRefuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
}()
private lazy var requiredDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "B. Required departure fuel", textFieldText: "")
}()
private lazy var requiredUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "C. Required uplift (B - A)", textFieldText: "")
}()
private lazy var actualUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "D. Actual uplift", textFieldText: "")
}()
private lazy var actualDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "E. Actual departure fuel", textFieldText: "")
}()
private lazy var refuelStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
preRefuel,
requiredDepartureFuel,
requiredUplift,
actualUplift,
actualDepartureFuel
])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
// MARK: Config
private func configureAutoLayout() {
NSLayoutConstraint.activate([
refuelStackView.topAnchor.constraint(equalTo: topAnchor, constant: 50),
refuelStackView.leftAnchor.constraint(equalTo: leftAnchor),
refuelStackView.rightAnchor.constraint(equalTo: rightAnchor),
refuelStackView.heightAnchor.constraint(equalToConstant: 300)
])
}
}
然后我有一个主视图将这些元素组合在一起:
class NewFuelSheetMainView: UIView {
override init(frame: CGRect) {
super.init(frame: .zero)
self.addSubview(mainStack)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var flightDetailsHeader: NewFuelSheetHeaderView = {
return NewFuelSheetHeaderView()
}()
private lazy var refuelView: NewFuelSheetRefuelInfoView = {
return NewFuelSheetRefuelInfoView()
}()
private lazy var mainStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [flightDetailsHeader, refuelView])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: topAnchor, constant: 30),
mainStack.leftAnchor.constraint(equalTo: leftAnchor),
mainStack.rightAnchor.constraint(equalTo: rightAnchor),
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
}
最后 VC 显示主视图:
class DataEntryViewController: I6ViewController {
// MARK: Init
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Lifecycle
override func viewDidLoad() {
view.backgroundColor = .white
}
// MARK: Properties
private lazy var mainView: NewFuelSheetMainView = {
let mainView = NewFuelSheetMainView()
mainView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainView)
return mainView
}()
// MARK: Configuration
private func configureAutoLayout() {
NSLayoutConstraint.activate([
mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
])
}
}
在我看来,(显然我的逻辑是有缺陷的,因为它不起作用!!)这里的关键部分是在主视图中,我在其中显示两个较小视图的堆栈。在这里,我明确地将堆栈设置为 .vertical,并将此垂直堆栈固定到主视图的顶部和底部。但是,不是我所期望的第二个视图出现在第一个视图下方,而是它们只是出现在另一个视图之上:
显然我在这里遗漏了一个关键点,但我看不到哪里。任何帮助将不胜感激。
如您所料,这与您的约束有关。这就是您当前的视图。 UIstackView 会根据其内容自行调整大小。需要给 UIViews 高度和宽度信息。
这是我调整后的结果
DataEntryViewController
mainView.topAnchor.constraint(equalTo: view.topAnchor),
mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
mainView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
NewFuelSheetMainView
mainStack.topAnchor.constraint(equalTo: view.topAnchor),
mainStack.leftAnchor.constraint(equalTo: view.leftAnchor),
mainStack.rightAnchor.constraint(equalTo: view.rightAnchor),
mainStack.bottomAnchor.constraint(equalTo: view.bottomAnchor),
NewFuelSheetRefuelInfoView
refuelStackView.leftAnchor.constraint(equalTo: leftAnchor),
refuelStackView.rightAnchor.constraint(equalTo: rightAnchor),
refuelStackView.heightAnchor.constraint(equalToConstant: 300)
NewFuelSheetHeaderView
fuelSheetHeaderStack.leftAnchor.constraint(equalTo: leftAnchor),
fuelSheetHeaderStack.rightAnchor.constraint(equalTo: rightAnchor),
fuelSheetHeaderStack.heightAnchor.constraint(equalToConstant: 200)
开发过程中的一些技巧:
- 为您的 UI 元素提供对比色背景,以便在 运行-time
时轻松查看帧
- 一次处理 一个 元素
- 为所有
UIView
子 class 设置 self.clipsToBounds = true
- 如果他们的子视图不可见,你知道你有约束问题
例如,让我们从您的 NewEditableFuelSheetField
...
开始
创建开发/临时视图控制器:
class ScratchViewController: UIViewController {
override func viewDidLoad() {
view.backgroundColor = UIColor(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
let v = NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
}
}
将 NO 更改为您的 NewEditableFuelSheetField
class,这是输出:
文本字段在那里,但我们无法从输出中得知。因此,让我们对您的 class:
进行一些更改
class NewEditableFuelSheetField: UIView {
var titleText: String?
var textFieldText: String?
init(titleText: String, textFieldText: String) {
super.init(frame: .zero)
self.titleText = titleText
self.textFieldText = textFieldText
self.addSubview(editableField)
// this was missing from the code in your question
configureAutoLayout()
// so we can see the frame at run-time
self.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var editableField: UIStackView = {
let title = UILabel()
title.text = self.titleText
let textField = UITextField()
textField.isEnabled = false
// so we can see the frames at run-time
title.backgroundColor = .yellow
textField.backgroundColor = .green
//
let stack = UIStackView(arrangedSubviews: [title, textField])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
editableField.heightAnchor.constraint(equalToConstant: 50)
])
}
}
好的...现在我们看到了标签和字段的框架...但是我们限制了视图leading/trailing 每边 20 点。那么,为什么我们看不到红色视图背景?
让我们在 init
中添加 clipsToBounds
:
self.addSubview(editableField)
// this was missing from the code in your question
configureAutoLayout()
// so we can see the frames at run-time
self.backgroundColor = .red
// set clipsToBounds
self.clipsToBounds = true
新输出:
嗯……显然不是我们想要的。如果我们使用 Debug View Hierarchy
,我们可以看到 NewEditableFuelSheetField
的实例的高度和宽度为零,并且其内容显示为“越界”。
您已将标签和字段添加到垂直堆栈视图,将该堆栈视图添加到 self
,并将其高度设置为 50
...但您没有提供堆栈视图相对于其父视图的任何约束。
让我们解决这个问题:
private func configureAutoLayout() {
NSLayoutConstraint.activate([
editableField.heightAnchor.constraint(equalToConstant: 50),
// constraints relative to superview (self)
editableField.topAnchor.constraint(equalTo: topAnchor),
editableField.leadingAnchor.constraint(equalTo: leadingAnchor),
editableField.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
呜呜呜!看来我们取得了一些进展。
现在让我们将 5 个 NewEditableFuelSheetField
个实例添加到垂直堆栈视图(间距为 8 以使其清晰):
class ScratchViewController: UIViewController {
private lazy var preRefuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
}()
private lazy var requiredDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "B. Required departure fuel", textFieldText: "")
}()
private lazy var requiredUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "C. Required uplift (B - A)", textFieldText: "")
}()
private lazy var actualUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "D. Actual uplift", textFieldText: "")
}()
private lazy var actualDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "E. Actual departure fuel", textFieldText: "")
}()
private lazy var refuelStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
preRefuel,
requiredDepartureFuel,
requiredUplift,
actualUplift,
actualDepartureFuel
])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override func viewDidLoad() {
view.backgroundColor = UIColor(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
view.addSubview(refuelStackView)
// for visual example
refuelStackView.spacing = 8
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
refuelStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
refuelStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
refuelStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
}
}
结果(我添加了一个深蓝色的虚线轮廓来显示堆栈视图的框架):
如果您的每个 UIView
子class 都遵循该开发过程(从最“内部”的观点开始),您应该会成功。
附带说明:在为要添加到堆栈视图(分别为垂直/水平)的视图提供大小(高度或宽度)约束时要小心,然后还要提供堆栈视图 .distribution = .fillEqually
AND 自己的高度/宽度约束。你最终可以说:
make each of 5 arranged subviews 50-pts in height
make the stack view 300-pts in height
fill equally
你得到 5 * 50 = 250
...这将与 stack view height = 300
冲突
我很难通过 UIStackView 获得预期的效果。这是我的设置:
带有文本字段的字段元素:
class NewEditableFuelSheetField: UIView {
var titleText: String?
var textFieldText: String?
init(titleText: String, textFieldText: String) {
super.init(frame: .zero)
self.titleText = titleText
self.textFieldText = textFieldText
self.addSubview(editableField)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var editableField: UIStackView = {
let title = UILabel()
title.text = self.titleText
let textField = UITextField()
textField.isEnabled = false
let stack = UIStackView(arrangedSubviews: [title, textField])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
editableField.heightAnchor.constraint(equalToConstant: 50)
])
}
}
具有固定值的字段:
class NewFixedFuelSheetField: UIView {
var title: String?
var detail: String?
init(title: String, detail: String) {
super.init(frame: .zero)
self.title = title
self.detail = detail
configureAutoLayout()
self.addSubview(fixedField)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var fixedField: UIStackView = {
let title = UILabel()
let detail = UILabel()
title.text = self.title
detail.text = self.detail
let stack = UIStackView(arrangedSubviews: [title, detail])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stack)
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
fixedField.heightAnchor.constraint(equalToConstant: 50)
])
}
}
Header 包含一堆不可编辑字段的视图:
class NewFuelSheetHeaderView: UIView {
// MARK: Init
override init(frame: CGRect) {
super.init(frame: .zero)
self.addSubview(fuelSheetHeaderStack)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Properties
// 'detail' text will be brought in from API in next ticket
private lazy var flightNumber: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Flight number", detail: "VS0101")
}()
private lazy var aircraftReg: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Aircraft reg", detail: "GAAAA")
}()
private lazy var date: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Date", detail: "01.01.21")
}()
private lazy var time: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Time", detail: "12:01")
}()
private let supplier: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Supplier", detail: "i6Staging, BAPCO")
}()
private let fuelGrade: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Fuel grade", detail: "Jet A")
}()
private let freezePoint: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Freeze point", detail: "-40")
}()
private let specificGravity: NewFixedFuelSheetField = {
return NewFixedFuelSheetField(title: "Specific gravity", detail: "0.793")
}()
private lazy var fuelSheetHeaderFirstRow: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
flightNumber,
aircraftReg,
date,
time
])
stack.axis = .horizontal
stack.distribution = .fillEqually
return stack
}()
private lazy var fuelSheetHeaderSecondRow: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
supplier,
fuelGrade,
freezePoint,
specificGravity
])
stack.axis = .horizontal
stack.distribution = .fillEqually
return stack
}()
private lazy var fuelSheetHeaderStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [fuelSheetHeaderFirstRow, fuelSheetHeaderSecondRow])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
// MARK: Configuration
private func configureAutoLayout() {
NSLayoutConstraint.activate([
fuelSheetHeaderStack.topAnchor.constraint(equalTo: self.topAnchor, constant: 50),
fuelSheetHeaderStack.leftAnchor.constraint(equalTo: leftAnchor),
fuelSheetHeaderStack.rightAnchor.constraint(equalTo: rightAnchor),
fuelSheetHeaderStack.heightAnchor.constraint(equalToConstant: 200)
])
}
}
最终需要放置在 header 下方的第二个视图:
class NewFuelSheetRefuelInfoView: UIView {
// MARK: Init
override init(frame: CGRect) {
super.init(frame: .zero)
self.addSubview(refuelStackView)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Properties
private lazy var preRefuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
}()
private lazy var requiredDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "B. Required departure fuel", textFieldText: "")
}()
private lazy var requiredUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "C. Required uplift (B - A)", textFieldText: "")
}()
private lazy var actualUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "D. Actual uplift", textFieldText: "")
}()
private lazy var actualDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "E. Actual departure fuel", textFieldText: "")
}()
private lazy var refuelStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
preRefuel,
requiredDepartureFuel,
requiredUplift,
actualUplift,
actualDepartureFuel
])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
// MARK: Config
private func configureAutoLayout() {
NSLayoutConstraint.activate([
refuelStackView.topAnchor.constraint(equalTo: topAnchor, constant: 50),
refuelStackView.leftAnchor.constraint(equalTo: leftAnchor),
refuelStackView.rightAnchor.constraint(equalTo: rightAnchor),
refuelStackView.heightAnchor.constraint(equalToConstant: 300)
])
}
}
然后我有一个主视图将这些元素组合在一起:
class NewFuelSheetMainView: UIView {
override init(frame: CGRect) {
super.init(frame: .zero)
self.addSubview(mainStack)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var flightDetailsHeader: NewFuelSheetHeaderView = {
return NewFuelSheetHeaderView()
}()
private lazy var refuelView: NewFuelSheetRefuelInfoView = {
return NewFuelSheetRefuelInfoView()
}()
private lazy var mainStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [flightDetailsHeader, refuelView])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: topAnchor, constant: 30),
mainStack.leftAnchor.constraint(equalTo: leftAnchor),
mainStack.rightAnchor.constraint(equalTo: rightAnchor),
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
}
最后 VC 显示主视图:
class DataEntryViewController: I6ViewController {
// MARK: Init
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
configureAutoLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Lifecycle
override func viewDidLoad() {
view.backgroundColor = .white
}
// MARK: Properties
private lazy var mainView: NewFuelSheetMainView = {
let mainView = NewFuelSheetMainView()
mainView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainView)
return mainView
}()
// MARK: Configuration
private func configureAutoLayout() {
NSLayoutConstraint.activate([
mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
])
}
}
在我看来,(显然我的逻辑是有缺陷的,因为它不起作用!!)这里的关键部分是在主视图中,我在其中显示两个较小视图的堆栈。在这里,我明确地将堆栈设置为 .vertical,并将此垂直堆栈固定到主视图的顶部和底部。但是,不是我所期望的第二个视图出现在第一个视图下方,而是它们只是出现在另一个视图之上:
显然我在这里遗漏了一个关键点,但我看不到哪里。任何帮助将不胜感激。
如您所料,这与您的约束有关。这就是您当前的视图。 UIstackView 会根据其内容自行调整大小。需要给 UIViews 高度和宽度信息。
这是我调整后的结果
DataEntryViewController
mainView.topAnchor.constraint(equalTo: view.topAnchor),
mainView.leftAnchor.constraint(equalTo: view.leftAnchor),
mainView.rightAnchor.constraint(equalTo: view.rightAnchor),
mainView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
NewFuelSheetMainView
mainStack.topAnchor.constraint(equalTo: view.topAnchor),
mainStack.leftAnchor.constraint(equalTo: view.leftAnchor),
mainStack.rightAnchor.constraint(equalTo: view.rightAnchor),
mainStack.bottomAnchor.constraint(equalTo: view.bottomAnchor),
NewFuelSheetRefuelInfoView
refuelStackView.leftAnchor.constraint(equalTo: leftAnchor),
refuelStackView.rightAnchor.constraint(equalTo: rightAnchor),
refuelStackView.heightAnchor.constraint(equalToConstant: 300)
NewFuelSheetHeaderView
fuelSheetHeaderStack.leftAnchor.constraint(equalTo: leftAnchor),
fuelSheetHeaderStack.rightAnchor.constraint(equalTo: rightAnchor),
fuelSheetHeaderStack.heightAnchor.constraint(equalToConstant: 200)
开发过程中的一些技巧:
- 为您的 UI 元素提供对比色背景,以便在 运行-time 时轻松查看帧
- 一次处理 一个 元素
- 为所有
UIView
子 class 设置self.clipsToBounds = true
- 如果他们的子视图不可见,你知道你有约束问题
例如,让我们从您的 NewEditableFuelSheetField
...
创建开发/临时视图控制器:
class ScratchViewController: UIViewController {
override func viewDidLoad() {
view.backgroundColor = UIColor(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
let v = NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
}
}
将 NO 更改为您的 NewEditableFuelSheetField
class,这是输出:
文本字段在那里,但我们无法从输出中得知。因此,让我们对您的 class:
进行一些更改class NewEditableFuelSheetField: UIView {
var titleText: String?
var textFieldText: String?
init(titleText: String, textFieldText: String) {
super.init(frame: .zero)
self.titleText = titleText
self.textFieldText = textFieldText
self.addSubview(editableField)
// this was missing from the code in your question
configureAutoLayout()
// so we can see the frame at run-time
self.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var editableField: UIStackView = {
let title = UILabel()
title.text = self.titleText
let textField = UITextField()
textField.isEnabled = false
// so we can see the frames at run-time
title.backgroundColor = .yellow
textField.backgroundColor = .green
//
let stack = UIStackView(arrangedSubviews: [title, textField])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private func configureAutoLayout() {
NSLayoutConstraint.activate([
editableField.heightAnchor.constraint(equalToConstant: 50)
])
}
}
好的...现在我们看到了标签和字段的框架...但是我们限制了视图leading/trailing 每边 20 点。那么,为什么我们看不到红色视图背景?
让我们在 init
中添加 clipsToBounds
:
self.addSubview(editableField)
// this was missing from the code in your question
configureAutoLayout()
// so we can see the frames at run-time
self.backgroundColor = .red
// set clipsToBounds
self.clipsToBounds = true
新输出:
嗯……显然不是我们想要的。如果我们使用 Debug View Hierarchy
,我们可以看到 NewEditableFuelSheetField
的实例的高度和宽度为零,并且其内容显示为“越界”。
您已将标签和字段添加到垂直堆栈视图,将该堆栈视图添加到 self
,并将其高度设置为 50
...但您没有提供堆栈视图相对于其父视图的任何约束。
让我们解决这个问题:
private func configureAutoLayout() {
NSLayoutConstraint.activate([
editableField.heightAnchor.constraint(equalToConstant: 50),
// constraints relative to superview (self)
editableField.topAnchor.constraint(equalTo: topAnchor),
editableField.leadingAnchor.constraint(equalTo: leadingAnchor),
editableField.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
呜呜呜!看来我们取得了一些进展。
现在让我们将 5 个 NewEditableFuelSheetField
个实例添加到垂直堆栈视图(间距为 8 以使其清晰):
class ScratchViewController: UIViewController {
private lazy var preRefuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "A. Pre-refuel FOB", textFieldText: "")
}()
private lazy var requiredDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "B. Required departure fuel", textFieldText: "")
}()
private lazy var requiredUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "C. Required uplift (B - A)", textFieldText: "")
}()
private lazy var actualUplift: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "D. Actual uplift", textFieldText: "")
}()
private lazy var actualDepartureFuel: NewEditableFuelSheetField = {
return NewEditableFuelSheetField(titleText: "E. Actual departure fuel", textFieldText: "")
}()
private lazy var refuelStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [
preRefuel,
requiredDepartureFuel,
requiredUplift,
actualUplift,
actualDepartureFuel
])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override func viewDidLoad() {
view.backgroundColor = UIColor(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
view.addSubview(refuelStackView)
// for visual example
refuelStackView.spacing = 8
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
refuelStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
refuelStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
refuelStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
}
}
结果(我添加了一个深蓝色的虚线轮廓来显示堆栈视图的框架):
如果您的每个 UIView
子class 都遵循该开发过程(从最“内部”的观点开始),您应该会成功。
附带说明:在为要添加到堆栈视图(分别为垂直/水平)的视图提供大小(高度或宽度)约束时要小心,然后还要提供堆栈视图 .distribution = .fillEqually
AND 自己的高度/宽度约束。你最终可以说:
make each of 5 arranged subviews 50-pts in height
make the stack view 300-pts in height
fill equally
你得到 5 * 50 = 250
...这将与 stack view height = 300