UIStackView distributing with equalCentering - ios

I have created UIStackView programmatically and added 2 views that have 2 child views each. Here are my sample codes:
let sv = UIStackView()
sv.axis = .horizontal
sv.alignment = .center
sv.spacing = Config.Dimensions.horizontalSpacing
sv.distribution = .equalCentering
sv.translatesAutoresizingMaskIntoConstraints = false
let viewCountStudent = UIView()
viewCountStudent.addSubview(studentCount)
viewCountStudent.addSubview(labelStudent)
studentCount.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
studentCount.leftAnchor.constraint(equalTo: viewCountStudent.leftAnchor).isActive = true
studentCount.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
labelStudent.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
labelStudent.leftAnchor.constraint(equalTo: studentCount.rightAnchor, constant: 8.0).isActive = true
labelStudent.rightAnchor.constraint(equalTo: viewCountStudent.rightAnchor).isActive = true
labelStudent.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
let viewCountLesson = UIView()
viewCountLesson.addSubview(lessonCount)
viewCountLesson.addSubview(labelLesson)
lessonCount.leftAnchor.constraint(equalTo: viewCountLesson.leftAnchor).isActive = true
lessonCount.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
lessonCount.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
labelLesson.leftAnchor.constraint(equalTo: lessonCount.rightAnchor, constant: 8.0).isActive = true
labelLesson.rightAnchor.constraint(equalTo: viewCountLesson.rightAnchor).isActive = true
labelLesson.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
labelLesson.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
sv.addArrangedSubview(viewCountLesson)
sv.addArrangedSubview(viewCountStudent)
sv.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 8.0).isActive = true
sv.leftAnchor.constraint(equalTo: divider.leftAnchor, constant: 16.0).isActive = true
sv.rightAnchor.constraint(equalTo: divider.rightAnchor, constant: -16.0).isActive = true
sv.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: -8.0).isActive = true
addSubview(sv)
The layout it gives is like this:
Above is the horizontal bar and below is the StackView. I wonder why the gap in between 2 views are not equally distributing by stack view. I am trying to center them with spacing distributing equally. Any idea?

This may help you understand...
Each "row" of three green labels is a Horizontal Stack View with Spacing: 8 and Distribution set to:
Fill
Fill Equally
Fill Proportionally
Equal Centering
Equal Spacing
As you can see, with Distribution: Equal Centering, the stack view arranges its subviews so their centers are equally spaced.
What you probably want is equal spacing on the sides and in-between:
To get that layout, use Distribution: Fill and add an empty "spacer" view in the stack, so you have:
spacer1 - viewCountLesson - spacer2 - viewCountStudent - spacer3
then set spacer2 width equal to spacer1 and spacer3 width equal to spacer1.
Here is the code used to create that:
class NewStackViewController: UIViewController {
let studentCount: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.text = "2"
return v
}()
let lessonCount: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.text = "1"
return v
}()
let labelStudent: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
v.text = "Students"
return v
}()
let labelLesson: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
v.text = "Lesson"
return v
}()
let divider: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .gray
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(divider)
NSLayoutConstraint.activate([
divider.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
divider.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
divider.heightAnchor.constraint(equalToConstant: 2.0),
divider.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
])
let sv = UIStackView()
sv.axis = .horizontal
sv.alignment = .fill
sv.spacing = 0 //Config.Dimensions.horizontalSpacing
sv.distribution = .fill
sv.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(sv)
NSLayoutConstraint.activate([
sv.leadingAnchor.constraint(equalTo: divider.leadingAnchor, constant: 16.0),
sv.trailingAnchor.constraint(equalTo: divider.trailingAnchor, constant: -16.0),
sv.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 8.0),
])
let viewCountStudent = UIView()
viewCountStudent.addSubview(studentCount)
viewCountStudent.addSubview(labelStudent)
studentCount.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
studentCount.leftAnchor.constraint(equalTo: viewCountStudent.leftAnchor).isActive = true
studentCount.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
labelStudent.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
labelStudent.leftAnchor.constraint(equalTo: studentCount.rightAnchor, constant: 8.0).isActive = true
labelStudent.rightAnchor.constraint(equalTo: viewCountStudent.rightAnchor).isActive = true
labelStudent.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
let viewCountLesson = UIView()
viewCountLesson.addSubview(lessonCount)
viewCountLesson.addSubview(labelLesson)
lessonCount.leftAnchor.constraint(equalTo: viewCountLesson.leftAnchor).isActive = true
lessonCount.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
lessonCount.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
labelLesson.leftAnchor.constraint(equalTo: lessonCount.rightAnchor, constant: 8.0).isActive = true
labelLesson.rightAnchor.constraint(equalTo: viewCountLesson.rightAnchor).isActive = true
labelLesson.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
labelLesson.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
let sp1 = spacerView()
let sp2 = spacerView()
let sp3 = spacerView()
sv.addArrangedSubview(sp1)
sv.addArrangedSubview(viewCountLesson)
sv.addArrangedSubview(sp2)
sv.addArrangedSubview(viewCountStudent)
sv.addArrangedSubview(sp3)
NSLayoutConstraint.activate([
sp2.widthAnchor.constraint(equalTo: sp1.widthAnchor, multiplier: 1.0),
sp3.widthAnchor.constraint(equalTo: sp1.widthAnchor, multiplier: 1.0),
])
[sp1, sp2, sp3, viewCountLesson, viewCountStudent, studentCount, labelStudent, lessonCount, labelLesson].forEach {
// set borderWidth to 1 to add borders to labels and views so we can see them
$0.layer.borderWidth = 0
$0.layer.borderColor = UIColor.lightGray.cgColor
// un-comment next line to set backgrounds to clear
//$0.backgroundColor = .clear
}
}
func spacerView() -> UIView {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .green
return v
}
}

Related

How to programmatically align elements in a Stackview using SnapKit?

I'm trying to achieve a specific design inside a custom reusable view.
(Something like this: )
So I pass an URL to retrive the image and I pass a String to add below.
Firstly, I want the entire view to be the width of the elements (if the text is long then the entire view will be wider), I don't know how to do that, the view seems to be the entire width of the screen.
Secondly, I want the items to be centered horizontally and vertically, what I tried does not work.
Here is my current code :
func initLayout() {
stackView.axis = NSLayoutConstraint.Axis.vertical
stackView.distribution = UIStackView.Distribution.fillEqually
stackView.alignment = UIStackView.Alignment.center
stackView.spacing = 10.0
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textContainer)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackView)
self.snp.makeConstraints { (make) -> Void in
make.height.equalTo(70)
}
self.stackView.snp.makeConstraints{ (make) -> Void in
make.edges.equalTo(self)
}
}
And it results in something like this:
As you can (or cannot) see, the view is centered in the middle of the screen, which is not what I want. The view should be the width of the text and everything centered inside this particular width, then I add it inside my VC and place it so it's leading.
if I understand well the is your constraint without Snapkit:
Set your objects under your class controller declaration:
let myImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleToFill
iv.clipsToBounds = true
iv.backgroundColor = .red
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let myLabel: UILabel = {
let label = UILabel()
label.text = "Débats"
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 30, weight: .semibold) // set your font size here
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Now in viewDidLoad set parameters and constraints:
myImageView.image = UIImage(named: "profilo") // set here your image
let myWidth = myLabel.intrinsicContentSize.width // This reveal only text width in label
view.addSubview(myLabel)
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 25).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(myImageView)
myImageView.widthAnchor.constraint(equalToConstant: myWidth).isActive = true
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: myLabel.topAnchor).isActive = true
myImageView.centerXAnchor.constraint(equalTo: myLabel.centerXAnchor).isActive = true
This is the result:
Intere code:
class Aiutotipo: UIViewController {
let myImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleToFill
iv.clipsToBounds = true
iv.backgroundColor = .red
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let myLabel: UILabel = {
let label = UILabel()
label.text = "Débats"
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 30, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
myImageView.image = UIImage(named: "profilo") // set here your image
let myWidth = myLabel.intrinsicContentSize.width // This reveal only text width in label
view.addSubview(myLabel)
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 25).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(myImageView)
myImageView.widthAnchor.constraint(equalToConstant: myWidth).isActive = true
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: myLabel.topAnchor).isActive = true
myImageView.centerXAnchor.constraint(equalTo: myLabel.centerXAnchor).isActive = true
}
}

Setting background color for customView has no effect

I have a customView which I display by after a button-tap. My problem is that setting .backgroundColor has no effect and it is just a clear background.
CustomView:
let wishWishView: WishStackView = {
let v = WishStackView()
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
AddViewButton:
#objc func addWishButtonTapped(){
self.view.addSubview(self.wishWishView)
wishWishView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
wishWishView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
WishWishView is just a simple UIView with a StackView inside of it.
My guess would be that it is a UIStackView, which does not support having a background color.
You can read more about why this happens, and possible workarounds here: https://stackoverflow.com/a/34868367/3992237
Your code is Totally wrong, first you set your view like this:
let wishWishView: UIView = {
let v = UIView()
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
now set your stackView, in my example I put in 2 label in vertical axis:
let stackView: UIStackView = {
let label1 = UILabel()
label1.text = "Label 1"
label1.textColor = .red
label1.font = UIFont.systemFont(ofSize: 16)
let label2 = UILabel()
label2.text = "Label 2"
label2.textColor = .black
label2.font = UIFont.systemFont(ofSize: 16)
let sV = UIStackView(arrangedSubviews: [label1, label2])
sV.axis = .vertical
sV.distribution = .fillEqually
sV.translatesAutoresizingMaskIntoConstraints = false
return sV
}()
after that set your button:
let buttonAddView: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add View", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .red
button.addTarget(self, action: #selector(addWishButtonTapped), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
in viewDidLoad add button and set constraints
view.addSubview(buttonAddView)
buttonAddView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
buttonAddView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
buttonAddView.heightAnchor.constraint(equalToConstant: 50).isActive = true
buttonAddView.widthAnchor.constraint(equalToConstant: 120).isActive = true
now write the function that add the view with stack view inside when the button is tapped with right constraints, in your function you call only the position of the view but not the size of it. Write your function like this:
#objc func addWishButtonTapped(){
view.addSubview(self.wishWishView)
wishWishView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
wishWishView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
wishWishView.heightAnchor.constraint(equalToConstant: 100).isActive = true
wishWishView.widthAnchor.constraint(equalToConstant: 200).isActive = true
wishWishView.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: wishWishView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: wishWishView.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: wishWishView.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: wishWishView.trailingAnchor, constant: -10).isActive = true
}
Now simply change background of the wishWishView to automatically set stackView background, and that's it...

UIScrollView overlaps other UIElements so they are not clickable anymore

Dear StackOverflowCommunity,
im currently working on a project, where I need to make a fully dynamic user interface.
For that, im building all of it programatically to make it as easy as possible.
I've now come to a problem which is as soon as I wrap my contentView(UIStackView) with an UIScrollView to make it scrollable, the scrollView is in front of all the other UIElements so that I can only scroll. I can not interact with Buttons, Sliders, Switches or anything, no event will get triggered.
I've literally done anything I could think of (working on that Problem for DAYS) but couldn't find any suiting answer whether on google directly nor on stack overflow or apple forums.
I'm pretty sure its a pretty small change that I just wasn't capable of thinking of. I really appreciate any of your help.
Structure is like this:
ViewController > UIScrollView > UIStackView > Item Wrappers (for example contains a UISwitch and a Describing Label) > Single UIElement
On User Interaction (for example choosing a different mode) the wrappers get removed and/or added to the view as the user needs.
I did only post the code that is somehow relevant for this problem (in my opinion). If you need any further information feel free to ask.
I maybe need to add:
As soon as I remove the UIScrollView and just add the StackView (named contentView in code) to the main view it all works just fine, I just can't scroll it which is a big problem as soon as I have more than like 5 element wrappers attached to the view.
var wrappers : Dictionary<String, UIView> = [:]
var elements : Dictionary<String, [UIView]> = [:]
var constraints : Dictionary = [String: [[NSLayoutConstraint]]]()
let contentView = UIStackView()
let states = [
"state_operating_hours",
"state_dim",
"state_brightness_sensor",
"state_operating_voltage",
"state_circuit_voltage",
"state_load_current_led",
"state_output_power",
"state_temperature",
"state_error",
"state_sw_version",
"state_hw_version"
]
let checkboxes = [
"summertime_wintertime",
"dali",
"error_output"
]
let sliders = [
"immediate_sensitivity",
"immediate_dawn",
"immediate_dim",
"immediate_day",
"immediate_dusk",
"immediate_night",
"aging"
]
let textInputs = [
"module_name",
"switch_delay"
]
let dropdowns = [
"mode",
"bt_state",
"config_output"
]
let timePickers = [
"phase_0",
"phase_1",
"phase_2",
"phase_3"
]
let buttons = [
"state_trigger",
"reset_trigger",
]
let l_kind_criteria = [
"immediate_dawn",
"immediate_day",
"immediate_dusk",
"immediate_night",
"immediate_sensitivity"
]
let d_kind_criteria = [
"immediate_dim"
]
let t_kind_criteria = [
"phase_0",
"phase_1",
"phase_2",
"phase_3"
]
let m_kind_criteria = [
"immediate_dawn",
"immediate_day",
"immediate_dusk",
"immediate_night",
"immediate_sensitivity",
"phase_0",
"phase_1"
]
let user_criteria = [
//"access",
//"state_trigger",
//"reset_trigger",
"mode",
"summertime_wintertime"
]
let service_criteria = [
"module_name",
//"access",
"state_trigger",
"reset_trigger",
"mode",
"bt_state",
"config_output",
"aging",
"switch_delay",
"summertime_wintertime",
"error_output",
"dali"
]
override func viewDidLoad() {
bleService.delegate = self
bleService.requestAuthMode()
view.backgroundColor = .lightGray
bleService.send(aText: "c28r:#")
bleService.send(aText: "c05r:#")
Toast.show(message: "Statuswerte werden abgerufen..." , controller: self)
buildLayout()
}
// Class - Functions
func buildLayout() {
// Building the Basic Layout
let topView = UIView()
topView.backgroundColor = .purple
self.view.addSubview(topView)
topView.translatesAutoresizingMaskIntoConstraints = false
let logoImageView = UIImageView(image: UIImage(named: "placeholder"))
logoImageView.translatesAutoresizingMaskIntoConstraints = false
logoImageView.frame = CGRect(x: 0, y: 0, width: view.frame.width/1.8, height: 30)
topView.addSubview(logoImageView)
logoImageView.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 20).isActive = true
logoImageView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 30).isActive = true
topView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
topView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
topView.heightAnchor.constraint(equalToConstant: view.frame.height/3).isActive = true
topView.centerYAnchor.constraint(equalTo: view.topAnchor).isActive = true
topView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
//Generate and add Scroll View to Main Window
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
//Add Content Stack to Scroll View
contentView.axis = .vertical
contentView.alignment = .fill
contentView.spacing = 150
contentView.distribution = .fill
contentView.backgroundColor = .blue
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 20),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -20),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.widthAnchor.constraint(equalToConstant: scrollView.frame.width),
contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
// programmatically creating layout elements without constraints
// Elements that change a value are always last in their respective array
for (index, dropdownName) in dropdowns.enumerated() {
constraints[dropdownName] = [[]]
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = dropdownName
let leadAnch = label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[dropdownName]!.append([leadAnch])
let textField = UITextField()
textField.delegate = self
textField.translatesAutoresizingMaskIntoConstraints = false
textField.backgroundColor = .white
textField.layer.cornerRadius = 5
var trailAnch = textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
var widthAnch = textField.widthAnchor.constraint(equalToConstant: view.frame.width / 6)
constraints[dropdownName]!.append([trailAnch, widthAnch])
let pickerView = UIPickerView()
pickerView.backgroundColor = .white
pickerView.translatesAutoresizingMaskIntoConstraints = false
pickerView.delegate = self
pickerView.isHidden = true
pickerView.dataSource = self
trailAnch = pickerView.trailingAnchor.constraint(equalTo: textField.trailingAnchor)
widthAnch = pickerView.widthAnchor.constraint(equalTo: textField.widthAnchor)
constraints[dropdownName]!.append([trailAnch, widthAnch])
let dropdownWrapper = UIView()
dropdownWrapper.translatesAutoresizingMaskIntoConstraints = false
dropdownWrapper.addSubview(label)
dropdownWrapper.addSubview(textField)
dropdownWrapper.addSubview(pickerView)
wrappers[dropdownName] = dropdownWrapper
elements[dropdownName] = [label, textField, pickerView]
let commandID = bleService.getCommand(commandName: dropdownName)
bleService.send(aText: "c\(commandID)r:#")
}
for (index, sliderName) in sliders.enumerated() {
constraints[sliderName] = [[]]
let descLabel = UILabel()
descLabel.translatesAutoresizingMaskIntoConstraints = false
descLabel.text = sliderName
var leadAnch = descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[sliderName]!.append([leadAnch])
let valueLabel = UILabel()
valueLabel.translatesAutoresizingMaskIntoConstraints = false
valueLabel.text = "0"
valueLabel.backgroundColor = .white
let widthAnch = valueLabel.widthAnchor.constraint(equalToConstant: view.frame.width/6)
var trailAnch = valueLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[sliderName]!.append([trailAnch, widthAnch])
let slider = UISlider()
slider.translatesAutoresizingMaskIntoConstraints = false
slider.isContinuous = false
slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
leadAnch = slider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
trailAnch = slider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
let topAnch = slider.topAnchor.constraint(equalTo: descLabel.bottomAnchor, constant: 5)
constraints[sliderName]!.append([trailAnch, leadAnch, topAnch])
let sliderWrapper = UIView()
sliderWrapper.translatesAutoresizingMaskIntoConstraints = false
sliderWrapper.addSubview(descLabel)
sliderWrapper.addSubview(valueLabel)
sliderWrapper.addSubview(slider)
wrappers[sliderName] = sliderWrapper
elements[sliderName] = [descLabel, valueLabel, slider]
let commandID = bleService.getCommand(commandName: sliderName)
bleService.send(aText: "c\(commandID)r:#")
}
for (index, checkboxName) in checkboxes.enumerated() {
constraints[checkboxName] = [[]]
let cbLabel = UILabel()
cbLabel.translatesAutoresizingMaskIntoConstraints = false
cbLabel.text = checkboxName
let leadAnch = cbLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[checkboxName]!.append([leadAnch])
let checkbox = UISwitch()
checkbox.translatesAutoresizingMaskIntoConstraints = false
checkbox.addTarget(self, action: #selector(checkboxClicked), for: .valueChanged)
let trailAnch = checkbox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[checkboxName]!.append([trailAnch])
let checkboxWrapper = UIView()
checkboxWrapper.translatesAutoresizingMaskIntoConstraints = false
checkboxWrapper.addSubview(cbLabel)
checkboxWrapper.addSubview(checkbox)
wrappers[checkboxName] = checkboxWrapper
elements[checkboxName] = [cbLabel, checkbox]
let commandID = bleService.getCommand(commandName: checkboxName)
bleService.send(aText: "c\(commandID)r:#")
}
for (index, textInputName) in textInputs.enumerated() {
constraints[textInputName] = [[]]
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.text = textInputName
var leadAnch = textLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[textInputName]!.append([leadAnch])
let inputField = UITextField()
inputField.layer.cornerRadius = 5
inputField.translatesAutoresizingMaskIntoConstraints = false
inputField.placeholder = textInputs[index]
inputField.backgroundColor = .white
inputField.addTarget(self, action: #selector(textfieldChanged), for: .valueChanged)
let topAnch = inputField.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 5)
let widthAnch = inputField.widthAnchor.constraint(equalToConstant: view.safeAreaLayoutGuide.layoutFrame.width/1.1)
leadAnch = inputField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[textInputName]!.append([topAnch, widthAnch, leadAnch])
let inputWrapper = UIView()
inputWrapper.translatesAutoresizingMaskIntoConstraints = false
inputWrapper.addSubview(textLabel)
inputWrapper.addSubview(inputField)
wrappers[textInputName] = inputWrapper
elements[textInputName] = [textLabel, inputField]
let commandID = bleService.getCommand(commandName: textInputName)
bleService.send(aText: "c\(commandID)r:#")
}
for(index, phase) in timePickers.enumerated() {
constraints[phase] = [[]]
let descLabel = UILabel()
descLabel.translatesAutoresizingMaskIntoConstraints = false
descLabel.text = "Zeitschaltung \(index+1)"
var leadAnch = descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[phase]!.append([leadAnch])
let enabledSwitch = UISwitch()
enabledSwitch.translatesAutoresizingMaskIntoConstraints = false
enabledSwitch.addTarget(self, action: #selector(changeTimerState), for: .valueChanged)
var topAnch = enabledSwitch.topAnchor.constraint(equalTo: descLabel.topAnchor)
var trailAnch = enabledSwitch.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[phase]!.append([trailAnch, topAnch])
let showPickerButton = UIButton()
showPickerButton.translatesAutoresizingMaskIntoConstraints = false
showPickerButton.setTitle("Zeit auswählen", for: .normal)
showPickerButton.backgroundColor = .darkGray
showPickerButton.layer.cornerRadius = 5
showPickerButton.addTarget(self, action: #selector(showTimePicker), for: .touchUpInside)
topAnch = showPickerButton.topAnchor.constraint(equalTo: enabledSwitch.bottomAnchor, constant: 4)
trailAnch = showPickerButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[phase]!.append([topAnch, trailAnch])
let timePicker = UIDatePicker()
timePicker.backgroundColor = .white
timePicker.isHidden = true
timePicker.translatesAutoresizingMaskIntoConstraints = false
timePicker.datePickerMode = .time
timePicker.addTarget(self, action: #selector(changeTimer), for: .valueChanged)
topAnch = timePicker.bottomAnchor.constraint(equalTo: enabledSwitch.bottomAnchor)
trailAnch = timePicker.trailingAnchor.constraint(equalTo: enabledSwitch.trailingAnchor)
constraints[phase]!.append([topAnch, trailAnch])
//Brightness Slider Value Label
let sliderValLabel = UILabel()
sliderValLabel.translatesAutoresizingMaskIntoConstraints = false
sliderValLabel.text = "0"
sliderValLabel.backgroundColor = .white
trailAnch = sliderValLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
topAnch = sliderValLabel.topAnchor.constraint(equalTo: showPickerButton.bottomAnchor, constant: 10)
var widthAnch = sliderValLabel.widthAnchor.constraint(equalToConstant: view.frame.width / 6)
constraints[phase]!.append([trailAnch, topAnch, widthAnch])
//Brightness Slider
let valueSlider = UISlider()
valueSlider.isContinuous = false
valueSlider.translatesAutoresizingMaskIntoConstraints = false
topAnch = valueSlider.topAnchor.constraint(equalTo: sliderValLabel.bottomAnchor, constant: 10)
leadAnch = valueSlider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
trailAnch = valueSlider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[phase]!.append([topAnch, leadAnch, trailAnch])
let timePickerWrapper = UIView()
//timePickerWrapper.translatesAutoresizingMaskIntoConstraints = false
timePickerWrapper.addSubview(descLabel)
timePickerWrapper.addSubview(enabledSwitch)
timePickerWrapper.addSubview(showPickerButton)
timePickerWrapper.addSubview(timePicker)
timePickerWrapper.addSubview(valueSlider)
timePickerWrapper.addSubview(sliderValLabel)
wrappers[phase] = timePickerWrapper
elements[phase] = [descLabel, showPickerButton, enabledSwitch, timePicker, sliderValLabel, valueSlider]
let commandID = bleService.getCommand(commandName: phase)
bleService.send(aText: "c\(commandID)r:#")
}
for buttonName in buttons {
constraints[buttonName] = [[]]
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
let widthAnch = button.widthAnchor.constraint(equalToConstant: contentView.frame.width/1.1)
let xAnch = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
constraints[buttonName]!.append([widthAnch, xAnch])
let buttonWrapper = UIView()
buttonWrapper.translatesAutoresizingMaskIntoConstraints = false
wrappers[buttonName] = buttonWrapper
elements[buttonName] = [button]
}
}
func changeContent(criteria: [String]) {
for item in criteria {
if(!contentView.contains(wrappers[item]!)) {
contentView.addArrangedSubview(wrappers[item]!)
for singleView in constraints[item]! {
for singleViewConstraint in singleView {
singleViewConstraint.isActive = true
}
}
}
}
}
func removeContent() {
var criteria = [String]()
switch(previousSetupMode) {
case "d":
criteria = d_kind_criteria
break
case "l":
criteria = l_kind_criteria
break
case "m":
criteria = m_kind_criteria
break
case "t":
criteria = t_kind_criteria
break
default:
break
}
for item in criteria {
wrappers[item]!.removeFromSuperview()
}
}
func changeView() {
if(previousSetupMode != activeSetupMode) {
removeContent()
}
switch(activeSetupMode) {
case "d":
changeContent(criteria: d_kind_criteria)
break
case "l":
changeContent(criteria: l_kind_criteria)
break
case "t":
changeContent(criteria: t_kind_criteria)
break
case "m":
changeContent(criteria: m_kind_criteria)
break
default:
break
}
}
The problem is that you are not giving your "wrapper" views any height, so the controls are being placed outside the bounds of their parent views.
You can confirm this two ways...
1) In your
for (index, sliderName) in sliders.enumerated() {
block, add:
sliderWrapper.backgroundColor = .green
(after creating the sliderWrapper view, of course). When you run the app, you won't see the green background, because sliderWrapper has a height of Zero:
2) And / or add:
sliderWrapper.clipsToBounds = true
and you won't see the controls at all:
To solve this, you can add constraints:
let sliderWrapper = UIView()
sliderWrapper.translatesAutoresizingMaskIntoConstraints = false
sliderWrapper.backgroundColor = .green
sliderWrapper.addSubview(descLabel)
sliderWrapper.addSubview(valueLabel)
sliderWrapper.addSubview(slider)
// add a topAnchor constraint from the top of descLabel to the top of sliderWrapper
// center valueLabel vertically to descLabel
// and a bottomAnchor from the bottom of slider to the bottom of sliderWrapper (negative if you want "padding")
NSLayoutConstraint.activate([
descLabel.topAnchor.constraint(equalTo: sliderWrapper.topAnchor, constant: 8.0),
valueLabel.centerYAnchor.constraint(equalTo: descLabel.centerYAnchor),
slider.bottomAnchor.constraint(equalTo: sliderWrapper.bottomAnchor, constant: -8.0),
])
Now, background is visible... controls are visible... and controls can be interacted with:

Issue with positioning views,labels, imageviews programmatically inside stackview

I am currently working on an app on iOS for my client. I ran into a specific issue which I cannot solve.
I am adding views programmatically to a stackview which is inside uiview which is inside scrollview.
Views inside stackview should have different heights depending on the contents of labels which are added inside each view inside stackview.
Some labels will contain html attributed text.
Some subviews of uistackview will contain images downloaded from internet at runtime using kingfisher library.
Please help me with this.
Current result
Storyboard and constraints
Setting frames, bounds etc does not work.
Using "setNeedsLayout", "layoutIfNeeded", "layoutSubViews" does not help and does not work.
self.innerDetailsContainer.translatesAutoresizingMaskIntoConstraints = false
detailsStackView.translatesAutoresizingMaskIntoConstraints = false
detailsStackView.distribution = .equalSpacing
detailsStackView.axis = .vertical
detailsStackView.alignment = .fill
detailsStackView.spacing = 5
statusItem.status = userOrder.statusName;
statusItem.errorText = userOrder.statusCommentError;
headerStatusItem.status = statusItem
let statusRootContainer:UIView = UIView()
detailsStackView.addArrangedSubview(statusRootContainer)
statusRootContainer.translatesAutoresizingMaskIntoConstraints = false
let statusContainer:UIView = UIView()
statusRootContainer.addSubview(statusContainer)
statusContainer.translatesAutoresizingMaskIntoConstraints = false
statusContainer.backgroundColor = UIColor.purple
statusContainer.topAnchor.constraint(equalTo: statusRootContainer.topAnchor, constant: mediumEdgeMargin).isActive = true
statusContainer.bottomAnchor.constraint(equalTo: statusRootContainer.bottomAnchor, constant: mediumEdgeMargin).isActive = true
statusContainer.leadingAnchor.constraint(equalTo: statusRootContainer.leadingAnchor, constant: mediumEdgeMargin).isActive = true
statusContainer.trailingAnchor.constraint(equalTo: statusRootContainer.trailingAnchor, constant: mediumEdgeMargin).isActive = true
let statusLabel:UILabel = UILabel()
statusContainer.addSubview(statusLabel)
statusLabel.translatesAutoresizingMaskIntoConstraints = false
statusLabel.textAlignment = .left
statusLabel.numberOfLines = 1
statusLabel.backgroundColor = UIColor.clear
statusLabel.textColor = UIColor.textColor
statusLabel.font = UIFont.systemFont(ofSize: (isIpad ? 24 : 18), weight: UIFont.Weight.semibold)
statusLabel.text = nil
statusLabel.text = userOrder.statusName
statusLabel.topAnchor.constraint(equalTo: statusContainer.topAnchor, constant: largeEdgeMargin).isActive = true
statusLabel.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: largeEdgeMargin).isActive = true
statusLabel.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: largeEdgeMargin).isActive = true
let statusContentLabel:UILabel = UILabel()
statusContainer.addSubview(statusContentLabel)
statusContentLabel.translatesAutoresizingMaskIntoConstraints = false
let htmlContentFont = UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.regular)
let htmlContentBoldFont = UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.bold)
statusContentLabel.text = nil
statusContentLabel.numberOfLines = 0
statusContentLabel.textAlignment = .left
statusContentLabel.lineBreakMode = .byWordWrapping
statusContentLabel.backgroundColor = UIColor.clear
statusContentLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: spacingForTitles).isActive = true
statusContentLabel.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: largeEdgeMargin).isActive = true
statusContentLabel.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: largeEdgeMargin).isActive = true
statusContentLabel.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: largeEdgeMargin).isActive = true
var contentText:String = ""
if let dateCreated = userOrder.createdAt, !dateCreated.isEmpty{
let dateCreatedSeconds = DateUtil.stringDateToSeconds(stringDate: dateCreated, dateFormatPattern: "yyyy-MM-dd HH:mm:ss")
if dateCreatedSeconds > 0 {
let dateCreatedFormatted = DateUtil.secondsToStringDate(seconds: dateCreatedSeconds, reulstDateFormatPattern: "dd.MM.yyyy HH:mm")
contentText = "\(NSLocalizedString("order_details_date_created_title", comment: "")) \(dateCreatedFormatted)"
}else{
contentText = "\(NSLocalizedString("order_details_date_created_title", comment: "")) \(dateCreated)"
}
}
var plannedShipmentResult = ""
if let plannedShipment = userOrder.deliveryDate, !plannedShipment.isEmpty,let plannedShipmentTimeRange = userOrder.deliveryTime, !plannedShipmentTimeRange.isEmpty {
let plannedShipmentSeconds = DateUtil.stringDateToSeconds(stringDate: plannedShipment, dateFormatPattern: "yyyy-MM-dd")
if plannedShipmentSeconds > 0 {
plannedShipmentResult = "\(DateUtil.secondsToStringDate(seconds: plannedShipmentSeconds, reulstDateFormatPattern: "dd.MM.yyyy")) \(plannedShipmentTimeRange)"
}else{
plannedShipmentResult = plannedShipment
}
let shipmentText = "\(NSLocalizedString("order_details_date_planned_shipment_title", comment: "")) \(plannedShipmentResult)"
if !contentText.isEmpty {
contentText = "\(contentText)<br>\(shipmentText)"
}else{
contentText = shipmentText
}
}
statusContentLabel.attributedText = contentText.simpleHtmlAttributedString(lineSpacing:0,fontColor: UIColor.textColor,font: htmlContentFont, bold: htmlContentBoldFont, italic: nil)
if let statusCommentError = userOrder.statusCommentError, !statusCommentError.isEmpty {
let errorContainer:UIView = UIView()
detailsStackView.addArrangedSubview(errorContainer)
errorContainer.translatesAutoresizingMaskIntoConstraints = false
let statusCommentErrorContainer:UIView = UIView()
errorContainer.addSubview(statusCommentErrorContainer)
statusCommentErrorContainer.translatesAutoresizingMaskIntoConstraints = false
statusCommentErrorContainer.backgroundColor = UIColor.errorRed
statusCommentErrorContainer.cornerRadius = 8
statusCommentErrorContainer.centerXAnchor.constraint(equalTo: errorContainer.centerXAnchor, constant: largeEdgeMargin).isActive = true
statusCommentErrorContainer.centerYAnchor.constraint(equalTo: errorContainer.centerYAnchor, constant: largeEdgeMargin).isActive = true
statusCommentErrorContainer.bottomAnchor.constraint(equalTo: errorContainer.bottomAnchor, constant: largeEdgeMargin).isActive = true
statusCommentErrorContainer.topAnchor.constraint(equalTo: errorContainer.topAnchor, constant: largeEdgeMargin).isActive = true
statusCommentErrorContainer.leadingAnchor.constraint(equalTo: errorContainer.leadingAnchor, constant: largeEdgeMargin).isActive = true
statusCommentErrorContainer.trailingAnchor.constraint(equalTo: errorContainer.trailingAnchor, constant: largeEdgeMargin).isActive = true
let statusBottomErrorContentLabel:UILabel = UILabel()
statusCommentErrorContainer.addSubview(statusBottomErrorContentLabel)
statusBottomErrorContentLabel.translatesAutoresizingMaskIntoConstraints = false
statusBottomErrorContentLabel.text = nil
statusBottomErrorContentLabel.numberOfLines = 0
statusBottomErrorContentLabel.textAlignment = .left
statusBottomErrorContentLabel.lineBreakMode = .byWordWrapping
statusBottomErrorContentLabel.backgroundColor = UIColor.circleGreen
let htmlErrorFont = UIFont.systemFont(ofSize: (isIpad ? 20 : 14), weight: UIFont.Weight.regular)
let htmlErrorBoldFont = UIFont.systemFont(ofSize: (isIpad ? 20 : 14), weight: UIFont.Weight.bold)
statusBottomErrorContentLabel.attributedText = statusCommentError.simpleHtmlAttributedString(fontColor: UIColor.white,font: htmlErrorFont, bold: htmlErrorBoldFont, italic: nil)
statusBottomErrorContentLabel.sizeToFit()
statusBottomErrorContentLabel.topAnchor.constraint(equalTo: statusCommentErrorContainer.topAnchor, constant: largeEdgeMargin).isActive = true
statusBottomErrorContentLabel.bottomAnchor.constraint(equalTo: statusCommentErrorContainer.bottomAnchor, constant: largeEdgeMargin).isActive = true
statusBottomErrorContentLabel.leadingAnchor.constraint(equalTo: statusCommentErrorContainer.leadingAnchor, constant: largeEdgeMargin).isActive = true
statusBottomErrorContentLabel.trailingAnchor.constraint(equalTo: statusCommentErrorContainer.trailingAnchor, constant: largeEdgeMargin).isActive = true
Desired result
I tried with tableview and automatic dimension but it did not resize subviews correctly at first load. At second load it was ok. There were issues with labels and incorrect height and with downloaded images. So I figured out that I could do this programmatically without tableview. I had 5 different complicated uitableviewcells. Some contained many labels and images and some only labels. I tried everything and it did not work:(

Setting up UI constraint (Image and stackView inside a UIView) programmatically in Swift

I'm trying to build a custom AD when the app opens it pop up some UIViews and image and two buttons then control it from my Firebase, for now I have problem adding the adImage and buttonsStack(contains 2 buttons) inside my backView programmatically and so far nothing works ..
I need the image takes ~ %75 of the backView up and the buttonsStack ~ %25 of the rest
here some code and I have upload it to my GitHub
import UIKit
class ViewController: UIViewController {
let backroundView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black
view.alpha = 0.5
return view
}()
let backView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.layer.cornerRadius = 15
return view
}()
let adImage: UIImageView = {
var image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.contentMode = .scaleAspectFill
return image
}()
let buttonsStack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.alignment = UIStackViewAlignment.fill
stack.axis = UILayoutConstraintAxis.vertical
stack.distribution = .equalSpacing
stack.spacing = 8
stack.backgroundColor = UIColor.red
return stack
}()
let actionButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Open", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = UIColor(red: 0, green: 0.60, blue: 1, alpha: 1)
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
let dismessButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Exit", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .lightGray
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI(){
// backroundView
view.addSubview(backroundView)
backroundView.frame = view.frame
// backView
view.addSubview(backView)
backView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80).isActive = true
backView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
backView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -25).isActive = true
backView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 25).isActive = true
// adImage
backView.addSubview(adImage)
adImage.image = UIImage(named: "testImage")
adImage.topAnchor.constraint(equalTo: backView.topAnchor).isActive = true
adImage.rightAnchor.constraint(equalTo: backView.rightAnchor).isActive = true
adImage.leftAnchor.constraint(equalTo: backView.leftAnchor).isActive = true
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
// buttonsStack
buttonsStack.addArrangedSubview(actionButton)
buttonsStack.addArrangedSubview(dismessButton)
backView.addSubview(buttonsStack)
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
buttonsStack.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -15).isActive = true
buttonsStack.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -15).isActive = true
buttonsStack.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 15).isActive = true
}
}
For the image to take 0.75 change this
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
to
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.75).isActive = true
//
then the buttonStack should goes under it so change this
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
to
buttonsStack.topAnchor.constraint(equalTo: adImage.bottomAnchor, constant: 15).isActive = true

Resources