Adjusting Uiview Height Inside Uistackview Control

Adjusting UIView Height Inside UIStackView Control

If you are trying to achieve a 20x20 square like the one below:

Sample Image

Then all you have to do is add another UIView to your green view. Make that UIView's size 20x20pts. Then change the small view background to green and the larger view's background to clear.

That said, this solution doesn't require a stackview and possibly over-complicates your UI. I would recommend just using autolayout instead.

Stackviews are most useful when

  1. you want to compensate for a change in device orientation,
  2. want to stack a lot of things on top of each other,
  3. want to create a very complicated layout with many elements but that has
    patterns you can repeat, or
  4. simply want to be able to remove an element or add an element in with the sizing correcting itself

That list isn't exhaustive, but should give you an idea of when and why you'd use a stack view.

Outside of a prototype cell

This behavior is trivial. You can simply set the constraints on your view:
Sample Image'

And then set the constraints on the stackview:
Sample Image

Hope this helps, cheers!

How to update the size of stackView after adding subviews (programatically)?

You should call sizeToFit() and layoutIfNeeded() on the subviews of the stackView. Constrain the UIStackView as you normally would, and constrain the subviews as you normally would.

Also, you need to have it set to Fill Proportionally and it will resize to fit the new content.

And Make sure you don't have a bottom constraint of a height constraint on the stackview. You should only need left, right and top constraint.

How to add fixed size UIView to UIStackView?

The main purpose of a UIStackView is to arrange its subviews.

So, you are constraining your stack view to "fill the screen" and then adding your "blue view" as an arranged subview ... at which point the stack view will "take over" the arrangement of the blue view.

Assuming you are using a stack view because you are planning on adding additional views, you can either allow the subviews to determine the stack view's frame (that is, don't constrain your stack view's width and/or height), or you need to change the stack view's .alignment and/or .distribution properties.

Here is a modification of your playground page to put the 100 x 100 blue view centered in the superview:

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {

override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view

// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical

view.addSubview(stackView)

// NSLayoutConstraint.activate([
// stackView.topAnchor.constraint(equalTo: view.topAnchor),
// stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
// ])

NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])

// view (100x100)
let fixedSizeView = UIView()
fixedSizeView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeView.backgroundColor = .blue

stackView.addArrangedSubview(fixedSizeView)

NSLayoutConstraint.activate([
fixedSizeView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeView.heightAnchor.constraint(equalToConstant: 100),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

and, here's a modification where two views - blue and red, each at 100 x 100 - get added to a stack view that is constrained to the top of the superview, with .alignment set to .center:

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {

override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view

// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .center

view.addSubview(stackView)

NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.topAnchor.constraint(equalTo: view.topAnchor),
])

// view (100x100)
let fixedSizeBlueView = UIView()
fixedSizeBlueView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeBlueView.backgroundColor = .blue

stackView.addArrangedSubview(fixedSizeBlueView)

NSLayoutConstraint.activate([
fixedSizeBlueView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeBlueView.heightAnchor.constraint(equalToConstant: 100),
])

// view (100x100)
let fixedSizeRedView = UIView()
fixedSizeRedView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeRedView.backgroundColor = .red

stackView.addArrangedSubview(fixedSizeRedView)

NSLayoutConstraint.activate([
fixedSizeRedView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeRedView.heightAnchor.constraint(equalToConstant: 100),
])

}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

UIStackView: consistent height based on all subviews even if they are missing

I don't think you want to try to use a stack view for this.

Instead, create a custom UIView, with three labels:

  • Top label
    • Top constrained to the view Top
  • Bottom label
    • Top constrained to the bottom of the Top label (with desired space)
    • Bottom constrained to the view Bottom
  • Center label
    • constrained centerY to the view

Then:

  • If both labels have text
    • show them and hide the center label
  • If only one label has text
    • give them both " " as text
    • hide them both
    • show the center label
    • set the center label's text and font to the respective top or bottom label

Here's an example:

Custom View

class WalterView: UIView {

let labelA = UILabel()
let labelB = UILabel()
let labelC = UILabel()

override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {

for (v, c) in zip([labelA, labelB, labelC], [UIColor.cyan, UIColor.green, UIColor.yellow]) {
addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.leadingAnchor.constraint(equalTo: leadingAnchor),
v.trailingAnchor.constraint(equalTo: trailingAnchor),
])
v.backgroundColor = c
v.textAlignment = .center
}

NSLayoutConstraint.activate([

labelA.topAnchor.constraint(equalTo: topAnchor),
labelB.topAnchor.constraint(equalTo: labelA.bottomAnchor, constant: 8.0),
labelB.bottomAnchor.constraint(equalTo: bottomAnchor),

labelC.centerYAnchor.constraint(equalTo: centerYAnchor),

])

labelA.text = " "
labelB.text = " "
labelC.isHidden = true

// set fonts as desired
labelA.font = .preferredFont(forTextStyle: .headline)
labelB.font = .preferredFont(forTextStyle: .subheadline)
labelA.adjustsFontForContentSizeCategory = true
labelB.adjustsFontForContentSizeCategory = true

}

func setLabels(topLabel strA: String, botLabel strB: String) -> Void {

if !strA.isEmpty && !strB.isEmpty {

// if neither string is empty
// show A & B
// set text for A & B
// hide C

labelA.text = strA
labelB.text = strB
labelC.text = ""
labelA.isHidden = false
labelB.isHidden = false
labelC.isHidden = true

} else {

// if either top or bottom string is empty
// hide A & B
// show C
// set A & B text to " " (so they're not empty)
// set text for C to the non-empty string
// set C's font & background color to respective top or bottom label

labelA.isHidden = true
labelB.isHidden = true
labelC.isHidden = false

labelA.text = " "
labelB.text = " "

if strA.isEmpty {
labelC.text = strB
labelC.backgroundColor = labelB.backgroundColor
guard let f = labelB.font else {
return
}
labelC.font = f
}

if strB.isEmpty {
labelC.text = strA
labelC.backgroundColor = labelA.backgroundColor
guard let f = labelA.font else {
return
}
labelC.font = f
}

}

}

}

Example Controller - each tap will cycle through "both", "Top Only" and "Bottom Only":

class WalterViewController: UIViewController {

var wView = WalterView()

var idx: Int = 0

var testStrings: [[String]] = [
["Top Label", "Bottom Label"],
["Top Only", ""],
["", "Bottom Only"],
]

override func viewDidLoad() {
super.viewDidLoad()

wView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(wView)

// respect safe area
let g = view.safeAreaLayoutGuide

NSLayoutConstraint.activate([
wView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
wView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
wView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),

// NO Height constraint...
// height will be determined by wView's labels
])

// so we can see the frame of wView
wView.layer.borderWidth = 1
wView.layer.borderColor = UIColor.red.cgColor

updateLabels()
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
updateLabels()
}

func updateLabels() -> Void {
let strs = testStrings[idx % testStrings.count]
wView.setLabels(topLabel: strs[0], botLabel: strs[1])
idx += 1
}

}

Results:

Sample Image

Sample Image

Sample Image



Related Topics



Leave a reply



Submit