Stack Overflow Asked by DevB1 on December 11, 2020
I am having difficulty getting the desired effect with UIStackView. Here is my setup:
Field element with textfield:
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)
])
}
}
Field with fixed values:
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 view containing a stack of non editable fields:
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)
])
}
}
Second view which ultimately needs to be placed beneath the 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)
])
}
}
Then I have a main view to bring these elements together:
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)
])
}
}
And finally a VC to display the main view:
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),
])
}
}
In my mind, (and clearly my logic is flawed as it’s not working!!) the key part here is in the main view where I present the stack of the two smaller views. Here I am clearly setting the stack as .vertical and I’m pinning this vertical stack to the top and bottom of the main view. However, rather than the second view appearing beneath the first which is what I would have expected, they are simply appearing one over the top of the other:
Clearly I’m missing a key point here but I can’t see where. Any help would be greatly appreciated.
As you could guess it's to do with your constraints. This is how your views look currently. UIstackViews are self-sizing to their content. UIViews need to be given height and width information.
This is after I made adjustments.
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)
Correct answer by Waylan Sands on December 11, 2020
A couple tips during development:
self.clipsToBounds = true
for all UIView
sub-classes - if their subviews are not visible, you know you have constraint problemsFor example, let's start with your NewEditableFuelSheetField
...
Create a development / scratch view controller:
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),
])
}
}
With NO changes to your NewEditableFuelSheetField
class, this is the output:
The text field is there, but we don't know that from looking at the output. So, let's make a couple changes to your 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)
])
}
}
OK... now we see the frames for the label and field... but we constrained the view leading/trailing with 20-pts on each side. So, why don't we see the red view background?
Let's add clipsToBounds
in init
:
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
the new output:
Hmm... obviously not what we want. If we use Debug View Hierarchy
we can see that the instance of NewEditableFuelSheetField
has a height and width of Zero, and its contents were showing "out-of-bounds."
You've added a label and a field to a vertical stack view, added that stack view to self
, and set its height to 50
... but you didn't give the stack view any constraints relative to its superview.
Let's fix that:
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),
])
}
Woo Hoo! Looks like we've made some progress.
Now let's add 5 NewEditableFuelSheetField
instances to a vertical stack view (with spacing of 8 to make it clear):
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),
])
}
}
Result (I added a dark-blue dashed-outline to show the frame of the stack view):
If you follow that development process with each of your UIView
subclasses (starting with the most "inside" views), you should be on your way.
As a side note: be careful when giving size (height or width) constraints to views you are adding to a stack view (vertical / horizontal respectively), and then ALSO giving the stack view .distribution = .fillEqually
AND its own height / width constraint. You can end up saying:
make each of 5 arranged subviews 50-pts in height
make the stack view 300-pts in height
fill equally
and you get 5 * 50 = 250
... which will conflict with stack view height = 300
Answered by DonMag on December 11, 2020
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP