Some time ago I asked how to draw UI block in this question. Thankfully to @HangarRash I got the answer and understanding how to do it. But right now I would like to created StackView which is based on two other stackviews. I have such code:
class ViewController: UIViewController {
@IBOutlet weak var mainContainer: UIView!
let colorDictionary = [
"Red":UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0),
"Green":UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0),
"Blue":UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0),
// "Green2":UIColor(red: 1.0, green: 0.7, blue: 0.0, alpha: 1.0),
]
//MARK: Instance methods
func colorButton(withColor color:UIColor, title:String) -> UILabel{
let newButton = UILabel()
newButton.backgroundColor = .gray
newButton.text = title
newButton.textAlignment = .center
newButton.textColor = UIColor.white
return newButton
}
func displayKeyboard(){
var buttonArray = [UILabel]()
for (myKey,myValue) in colorDictionary{
buttonArray += [colorButton(withColor: myValue, title: myKey)]
}
let horizontalStack = UIStackView(arrangedSubviews: buttonArray)
horizontalStack.axis = .horizontal
horizontalStack.distribution = .fillEqually
horizontalStack.alignment = .fill
horizontalStack.translatesAutoresizingMaskIntoConstraints = false
let label2 = UILabel()
label2.text = "Label"
label2.backgroundColor = .red
label2.textColor = .white
label2.textAlignment = .center
label2.lineBreakMode = .byCharWrapping
label2.numberOfLines = 0
label2.translatesAutoresizingMaskIntoConstraints = false
let leftStack = UIStackView()
leftStack.backgroundColor = .blue
leftStack.axis = .vertical
leftStack.distribution = .equalSpacing
leftStack.translatesAutoresizingMaskIntoConstraints = false
leftStack.addArrangedSubview(label2)
leftStack.addArrangedSubview(horizontalStack)
leftStack.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
let bottomStackView = UIStackView(arrangedSubviews: buttonArray)
bottomStackView.axis = .horizontal
bottomStackView.distribution = .fillEqually
bottomStackView.alignment = .fill
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
let url = URL(string: "https://picsum.photos/270")!
let image = UIImageView()
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, _, error in
if let data = data {
DispatchQueue.main.async {
image.image = UIImage(data: data)
image.contentMode = .scaleToFill
image.translatesAutoresizingMaskIntoConstraints = false
}
}
}.resume()
stackView.addArrangedSubview(image)
stackView.addArrangedSubview(bottomStackView)
let mainStackView = UIStackView()
mainStackView.axis = .horizontal
mainStackView.addArrangedSubview(leftStack)
mainStackView.addArrangedSubview(stackView)
mainContainer.addSubview(mainStackView)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mainStackView.topAnchor.constraint(equalTo: mainContainer.topAnchor, constant: 5),
mainStackView.leftAnchor.constraint(equalTo: mainContainer.leftAnchor),
mainStackView.rightAnchor.constraint(equalTo: mainContainer.rightAnchor),
mainStackView.heightAnchor.constraint(equalToConstant: 270),
leftStack.widthAnchor.constraint(equalToConstant: 270),
])
}
override func viewDidLoad() {
super.viewDidLoad()
displayKeyboard()
}
}
which draws me such screen:
but I don't understand where is my left ui block with 4 different labels and how to make stackview ui proportionally filled. I mean that I don't need to huge left view, I need it about 10% of the main stackview. I tried to make it in such way:
leftStack.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true
but it does not help me. I will need like 10% of the left block and 90% of main block with the image. I thought it is possible to set proportions for the views inside the stackview
The problem you are running into is related to how transforms and auto-layout interact - or, perhaps better said, don't interact.
From Apple's docs:
In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame.
So, when you rotate the stack view, its untransformed frame is used.
Quick example... Let's put three labels in a horizontal stack view and apply a rotation transform to the center one:
class Step1VC: UIViewController {
let leftLabel = UILabel()
let centerLabel = UILabel()
let rightLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let mainStackView = UIStackView()
mainStackView.axis = .horizontal
mainStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainStackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
mainStackView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
// add three labels to the stack view
leftLabel.textAlignment = .center
leftLabel.text = "Left"
leftLabel.backgroundColor = .yellow
centerLabel.textAlignment = .center
centerLabel.text = "Let's rotate this label"
centerLabel.backgroundColor = .green
rightLabel.textAlignment = .center
rightLabel.text = "Right"
rightLabel.backgroundColor = .cyan
mainStackView.addArrangedSubview(leftLabel)
mainStackView.addArrangedSubview(centerLabel)
mainStackView.addArrangedSubview(rightLabel)
// outline the stack view so we can see its frame
mainStackView.layer.borderColor = UIColor.red.cgColor
mainStackView.layer.borderWidth = 1
// info label
let iLabel = UILabel()
iLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
iLabel.numberOfLines = 0
iLabel.textAlignment = .center
iLabel.text = "\nStep 1\n\nTap anywhere to rotate center label\n"
iLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(iLabel)
NSLayoutConstraint.activate([
iLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -60.0),
iLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
iLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if centerLabel.transform == .identity {
centerLabel.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
} else {
centerLabel.transform = .identity
}
}
}
Here's what we get:
There are various ways to get around this... thinking about your end-goal, let's create a UIView
subclass with a label that will auto-adjust itself when transformed based on the label's frame.
So, custom class:
class MyCustomLabelView: UIView {
// public properties to replicate UILabel
// add any additional if needed
public var text: String = "" {
didSet { theLabel.text = text }
}
public var textColor: UIColor = .black {
didSet { theLabel.textColor = textColor }
}
public var font: UIFont = .systemFont(ofSize: 17.0) {
didSet { theLabel.font = font }
}
public var textAlignment: NSTextAlignment = .left {
didSet { theLabel.textAlignment = textAlignment }
}
override var backgroundColor: UIColor? {
didSet {
theLabel.backgroundColor = backgroundColor
super.backgroundColor = .clear
}
}
private let theLabel = UILabel()
public func rotateTo(_ d: Double) {
if let v = subviews.first {
// set the rotation transform
if d == 0 {
self.transform = .identity
} else {
self.transform = CGAffineTransform(rotationAngle: d)
}
// remove the label
v.removeFromSuperview()
// tell it to layout itself
v.setNeedsLayout()
v.layoutIfNeeded()
// get the frame of the label
// apply the same transform
let r = v.frame.applying(self.transform)
wC.isActive = false
hC.isActive = false
// add the label back
addSubview(v)
// set self's width and height anchors
// to the width and height of the label
wC = self.widthAnchor.constraint(equalToConstant: r.width)
hC = self.heightAnchor.constraint(equalToConstant: r.height)
// apply the new constraints
NSLayoutConstraint.activate([
v.centerXAnchor.constraint(equalTo: self.centerXAnchor),
v.centerYAnchor.constraint(equalTo: self.centerYAnchor),
wC, hC
])
}
}
private var wC: NSLayoutConstraint!
private var hC: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
backgroundColor = .clear
theLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(theLabel)
wC = self.widthAnchor.constraint(equalTo: theLabel.widthAnchor)
hC = self.heightAnchor.constraint(equalTo: theLabel.heightAnchor)
NSLayoutConstraint.activate([
theLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
theLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
wC, hC,
])
}
}
and the same controller as Step1
but we'll use three MyCustomLabelView
instead of three UILabel
:
class Step2VC: UIViewController {
let leftLabel = MyCustomLabelView()
let centerLabel = MyCustomLabelView()
let rightLabel = MyCustomLabelView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let mainStackView = UIStackView()
mainStackView.axis = .horizontal
mainStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainStackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
mainStackView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
// add three labels to the stack view
leftLabel.textAlignment = .center
leftLabel.text = "Left"
leftLabel.backgroundColor = .yellow
centerLabel.textAlignment = .center
centerLabel.text = "Let's rotate this label"
centerLabel.backgroundColor = .green
rightLabel.textAlignment = .center
rightLabel.text = "Right"
rightLabel.backgroundColor = .cyan
mainStackView.addArrangedSubview(leftLabel)
mainStackView.addArrangedSubview(centerLabel)
mainStackView.addArrangedSubview(rightLabel)
// outline the stack view so we can see its frame
mainStackView.layer.borderColor = UIColor.red.cgColor
mainStackView.layer.borderWidth = 1
// info label
let iLabel = UILabel()
iLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
iLabel.numberOfLines = 0
iLabel.textAlignment = .center
iLabel.text = "\nStep 2\n\nTap anywhere to rotate center label\n"
iLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(iLabel)
NSLayoutConstraint.activate([
iLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -60.0),
iLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
iLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if centerLabel.transform == .identity {
centerLabel.rotateTo(-.pi * 0.5)
} else {
centerLabel.rotateTo(0)
}
}
}
Now when we rotate the center label (view), we get this:
So, to get the full layout you're looking for, we'll create a custom view that contains the "left-side" labels (in a couple stack views), and an image view, a stack view for the bottom labels, and an "outer" stack view to hold everything together.
Custom "left-side" class:
class MyCustomView: UIView {
public var titleText: String = "" {
didSet { titleLabel.text = titleText }
}
public func addLabel(_ v: UIView) {
labelsStack.addArrangedSubview(v)
}
public func rotateTo(_ d: Double) {
// get the container view (in this case, it's the outer stack view)
if let v = subviews.first {
// set the rotation transform
if d == 0 {
self.transform = .identity
} else {
self.transform = CGAffineTransform(rotationAngle: d)
}
// remove the container view
v.removeFromSuperview()
// tell it to layout itself
v.setNeedsLayout()
v.layoutIfNeeded()
// get the frame of the container view
// apply the same transform as self
let r = v.frame.applying(self.transform)
wC.isActive = false
hC.isActive = false
// add it back
addSubview(v)
// set self's width and height anchors
// to the width and height of the container
wC = self.widthAnchor.constraint(equalToConstant: r.width)
hC = self.heightAnchor.constraint(equalToConstant: r.height)
// apply the new constraints
NSLayoutConstraint.activate([
v.centerXAnchor.constraint(equalTo: self.centerXAnchor),
v.centerYAnchor.constraint(equalTo: self.centerYAnchor),
wC, hC
])
}
}
// our subviews
private let outerStack = UIStackView()
private let titleLabel = UILabel()
private let labelsStack = UIStackView()
private var wC: NSLayoutConstraint!
private var hC: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// stack views and label properties
outerStack.axis = .vertical
outerStack.distribution = .fillEqually
labelsStack.axis = .horizontal
labelsStack.distribution = .fillEqually
titleLabel.textAlignment = .center
titleLabel.backgroundColor = .lightGray
titleLabel.textColor = .white
// add title label and labels stack to outer stack
outerStack.addArrangedSubview(titleLabel)
outerStack.addArrangedSubview(labelsStack)
outerStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(outerStack)
wC = self.widthAnchor.constraint(equalTo: outerStack.widthAnchor)
hC = self.heightAnchor.constraint(equalTo: outerStack.heightAnchor)
NSLayoutConstraint.activate([
outerStack.centerXAnchor.constraint(equalTo: self.centerXAnchor),
outerStack.centerYAnchor.constraint(equalTo: self.centerYAnchor),
wC, hC,
])
}
}
and an example controller:
class Step3VC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
guard let img = UIImage(named: "testPic") else {
fatalError("Need an image!")
}
// create the image view
let imgView = UIImageView()
imgView.contentMode = .scaleToFill
imgView.backgroundColor = .systemBlue
imgView.image = img
// create the "main" stack view
let mainStackView = UIStackView()
mainStackView.axis = .horizontal
// create the "right-side" stack view
let rightSideStack = UIStackView()
rightSideStack.axis = .vertical
// create the "bottom labels" stack view
let bottomLabelsStack = UIStackView()
bottomLabelsStack.distribution = .fillEqually
// add the image view and bottom labels stack view
// to the right-side stack view
rightSideStack.addArrangedSubview(imgView)
rightSideStack.addArrangedSubview(bottomLabelsStack)
// create the custom "left-side" view
let myView = MyCustomView()
// add views to the main stack view
mainStackView.addArrangedSubview(myView)
mainStackView.addArrangedSubview(rightSideStack)
// add main stack view to view
mainStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainStackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain Top/Leading/Trailing
mainStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
mainStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
mainStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// main stack view height will be determined by its subviews
])
// setup the left-side custom view
myView.titleText = "Gefährdung"
let titles: [String] = [
"keine / gering", "mittlere", "erhöhte", "hohe",
]
let colors: [UIColor] = [
UIColor(red: 0.863, green: 0.894, blue: 0.527, alpha: 1.0),
UIColor(red: 0.942, green: 0.956, blue: 0.767, alpha: 1.0),
UIColor(red: 0.728, green: 0.828, blue: 0.838, alpha: 1.0),
UIColor(red: 0.499, green: 0.706, blue: 0.739, alpha: 1.0),
]
for (c, t) in zip(colors, titles) {
myView.addLabel(colorLabel(withColor: c, title: t, titleColor: .black))
}
// rotate the left-side custom view 90-degrees counter-clockwise
myView.rotateTo(-.pi * 0.5)
// setup the bottom labels
let colorDictionary = [
"Red":UIColor.systemRed,
"Green":UIColor.systemGreen,
"Blue":UIColor.systemBlue,
]
for (myKey,myValue) in colorDictionary {
bottomLabelsStack.addArrangedSubview(colorLabel(withColor: myValue, title: myKey, titleColor: .white))
}
// info label
let iLabel = UILabel()
iLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
iLabel.numberOfLines = 0
iLabel.textAlignment = .center
iLabel.text = "\nStep 3\n"
iLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(iLabel)
NSLayoutConstraint.activate([
iLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -60.0),
iLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
iLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
])
}
func colorLabel(withColor color:UIColor, title:String, titleColor:UIColor) -> UILabel {
let newLabel = UILabel()
newLabel.backgroundColor = color
newLabel.text = title
newLabel.textAlignment = .center
newLabel.textColor = titleColor
return newLabel
}
}
The result:
To improve the visual a bit, I wanted a little "padding" on the labels... so, I used this simple label subclass:
class PaddedLabel: UILabel {
var padding: UIEdgeInsets = .zero
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: padding))
}
override var intrinsicContentSize : CGSize {
let sz = super.intrinsicContentSize
return CGSize(width: sz.width + padding.left + padding.right, height: sz.height + padding.top + padding.bottom)
}
}
Replaced the colorLabel(...)
func with this:
func colorLabel(withColor color:UIColor, title:String, titleColor:UIColor) -> UILabel {
let newLabel = PaddedLabel()
newLabel.padding = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8)
newLabel.backgroundColor = color
newLabel.text = title
newLabel.textAlignment = .center
newLabel.textColor = titleColor
return newLabel
}
and get this final result:
Edit - based on comments...
Slightly modified custom view:
class MyCustomView: UIView {
public var titleText: String = "" {
didSet { titleLabel.text = titleText }
}
public func addLabel(_ v: UIView) {
labelsStack.addArrangedSubview(v)
}
public func rotateTo(_ d: Double) {
// get the "container" view (in this case, it's the outer stack view)
if let v = subviews.first, v == outerStack {
// set the rotation transform
if d == 0 {
self.transform = .identity
} else {
self.transform = CGAffineTransform(rotationAngle: d)
}
// remove the container view
v.removeFromSuperview()
// tell it to layout itself
v.setNeedsLayout()
v.layoutIfNeeded()
// get the frame of the container view
// apply the same transform as self
let r = v.frame.applying(self.transform)
wC.isActive = false
hC.isActive = false
// add it back
addSubview(v)
// set self's width and height anchors
// to the width and height of the container
wC = self.widthAnchor.constraint(equalToConstant: r.width)
// this will vertically fit to self's superview
// if self's superview does not have a constrained height,
// self will be sized to the labels combined width
// safely unwrap the superview
if let sv = self.superview {
hC = outerStack.widthAnchor.constraint(equalTo: sv.heightAnchor)
} else {
hC = self.heightAnchor.constraint(equalToConstant: r.height)
}
// apply the new constraints
NSLayoutConstraint.activate([
v.centerXAnchor.constraint(equalTo: self.centerXAnchor),
v.centerYAnchor.constraint(equalTo: self.centerYAnchor),
wC, hC,
])
}
}
// our subviews
private let outerStack = UIStackView()
private let titleLabel = UILabel()
private let labelsStack = UIStackView()
private var wC: NSLayoutConstraint!
private var hC: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// stack views and label properties
outerStack.axis = .vertical
outerStack.distribution = .fillEqually
labelsStack.axis = .horizontal
labelsStack.distribution = .fillEqually
titleLabel.textAlignment = .center
titleLabel.backgroundColor = .lightGray
titleLabel.textColor = .white
// add title label and labels stack to outer stack
outerStack.addArrangedSubview(titleLabel)
outerStack.addArrangedSubview(labelsStack)
outerStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(outerStack)
wC = self.widthAnchor.constraint(equalTo: outerStack.widthAnchor)
hC = self.heightAnchor.constraint(equalTo: outerStack.heightAnchor)
NSLayoutConstraint.activate([
outerStack.centerXAnchor.constraint(equalTo: self.centerXAnchor),
outerStack.centerYAnchor.constraint(equalTo: self.centerYAnchor),
wC, hC,
])
}
}
Modified controller:
class ViewController: UIViewController {
// make this a class property
// so we can reference it outside of viewDidLoad
let mainStackView = UIStackView()
// for demo purposes
let minImageViewHeight: CGFloat = 200.0
// we'll be updating the constant on this to show that
// the overall height is controlled by the image view height
var ivHeightConstraint: NSLayoutConstraint!
// we use this to "toggle" between increasing / decreasing the height
var plusMinus: CGFloat = 1.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// let's increment or decrement the image view Height by 50
// we want 20-points top and bottom within the safe area
let safeHeight: CGFloat = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom)
let maxHeight: CGFloat = safeHeight - 40.0
let proposedNewHeight: CGFloat = mainStackView.frame.height + (50.0 * plusMinus)
var currentHeight: CGFloat = ivHeightConstraint.constant
if proposedNewHeight > maxHeight {
currentHeight += (50.0 - (proposedNewHeight - maxHeight)) + 50.0
//ivHeight += 50.0
plusMinus = -1.0
} else if plusMinus == -1.0, currentHeight - 50.0 < minImageViewHeight {
currentHeight = minImageViewHeight - 50.0
plusMinus = 1.0
}
currentHeight += (50.0 * plusMinus)
ivHeightConstraint.constant = currentHeight
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
guard let img = UIImage(named: "testPic") else {
fatalError("Need an image!")
}
// create the image view
let imgView = UIImageView()
imgView.contentMode = .scaleToFill
imgView.backgroundColor = .systemBlue
imgView.image = img
// "main" stack view axis
mainStackView.axis = .horizontal
// create the "right-side" stack view
let rightSideStack = UIStackView()
rightSideStack.axis = .vertical
// create the "bottom labels" stack view
let bottomLabelsStack = UIStackView()
bottomLabelsStack.distribution = .fillEqually
// add the image view and bottom labels stack view
// to the right-side stack view
rightSideStack.addArrangedSubview(imgView)
rightSideStack.addArrangedSubview(bottomLabelsStack)
// create the custom "left-side" view
let myView = MyCustomView()
// add views to the main stack view
mainStackView.addArrangedSubview(myView)
mainStackView.addArrangedSubview(rightSideStack)
// add main stack view to view
mainStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainStackView)
let g = view.safeAreaLayoutGuide
ivHeightConstraint = imgView.heightAnchor.constraint(equalToConstant: minImageViewHeight)
NSLayoutConstraint.activate([
// constrain Top/Leading/Trailing
mainStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
mainStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
mainStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// we can set the main stack view height here
//mainStackView.heightAnchor.constraint(equalToConstant: 600.0),
// or, set the image view height
// for this example, we'll be setting (and changing) the image view's height
ivHeightConstraint,
])
// setup the left-side custom view
myView.titleText = "Gefährdung"
let titles: [String] = [
"keine / gering", "mittlere", "erhöhte", "hohe",
]
let colors: [UIColor] = [
UIColor(red: 0.863, green: 0.894, blue: 0.527, alpha: 1.0),
UIColor(red: 0.942, green: 0.956, blue: 0.767, alpha: 1.0),
UIColor(red: 0.728, green: 0.828, blue: 0.838, alpha: 1.0),
UIColor(red: 0.499, green: 0.706, blue: 0.739, alpha: 1.0),
]
for (c, t) in zip(colors, titles) {
myView.addLabel(colorLabel(withColor: c, title: t, titleColor: .black))
}
// rotate the left-side custom view 90-degrees counter-clockwise
myView.rotateTo(-.pi * 0.5)
// setup the bottom labels
let colorDictionary = [
"Red":UIColor.systemRed,
"Green":UIColor.systemGreen,
"Blue":UIColor.systemBlue,
]
for (myKey,myValue) in colorDictionary {
bottomLabelsStack.addArrangedSubview(colorLabel(withColor: myValue, title: myKey, titleColor: .white))
}
}
func colorLabel(withColor color:UIColor, title:String, titleColor:UIColor) -> UILabel {
let newLabel = PaddedLabel()
newLabel.padding = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8)
newLabel.backgroundColor = color
newLabel.text = title
newLabel.textAlignment = .center
newLabel.textColor = titleColor
// note: we need to set vertical content hugging priority
// so the label don't "stretch"
newLabel.setContentHuggingPriority(.required, for: .vertical)
return newLabel
}
}
Padded label subclass:
class PaddedLabel: UILabel {
var padding: UIEdgeInsets = .zero
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: padding))
}
override var intrinsicContentSize : CGSize {
let sz = super.intrinsicContentSize
return CGSize(width: sz.width + padding.left + padding.right, height: sz.height + padding.top + padding.bottom)
}
}
So, with these modifications, we'll use the Height of the image view to determine the overall height, and we'll let the rotated "left-side" custom view fit to the mainStackView
.
We start with an imageView height of 200... each tap will increase that height by 50-points, until we reach a max-height (safe-area minus 20-points top & bottom), at which point each tap will decrease the imageView height.
Looks about like this: