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
2
Answers
First, create the two child stack views and configure them as desired. For example:
Next, create the horizontal stack view and add the two child stack views as arranged subviews:
Finally, add the horizontal stack view to your view hierarchy and configure its constraints as desired. For example:
This will create a horizontal stack view with two child stack views that are arranged side-by-side, filling the entire width of the parent view. You can then add views to the child stack views as needed to create your layout.
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:
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:
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:
and the same controller as
Step1
but we’ll use threeMyCustomLabelView
instead of threeUILabel
: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:
and an example controller:
The result:
To improve the visual a bit, I wanted a little "padding" on the labels… so, I used this simple label subclass:
Replaced the
colorLabel(...)
func with this:and get this final result:
Edit – based on comments…
Slightly modified custom view:
Modified controller:
Padded label subclass:
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: