How to correctly add safe area inset to UIStackView? - ios

So I have a simple stack view with two text inside it and I have added safe area constraints,
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let label = UILabel()
label.text = "Hello, World!"
label.sizeToFit()
label.translatesAutoresizingMaskIntoConstraints = false
let label2 = UILabel()
label2.text = "Hello, World!"
label2.sizeToFit()
label2.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView()
stackView.axis = .vertical
stackView.addArrangedSubview(label)
stackView.addArrangedSubview(label2)
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}
}
But it has big amount of space between two text. How do I remove that space? This only happens when I add this line stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).

The problem is that the StackView's making it so the elements in it are filling up the space equally.
Try setting: stackView.distribution = .fill
Here's an article about how StackView works and why that's happening to you: https://spin.atomicobject.com/2016/06/22/uistackview-distribution/

Related

UIImageView ignores widthAnchor inside UIStackView

I'm trying to align and scale an image inside a UIStackView:
class LogoLine: UIViewController {
override func viewDidLoad() {
let label = UILabel()
label.text = "powered by"
label.textColor = .label
label.textAlignment = .center
let logoToUse = UIImage(named: "Image")
let imageView = UIImageView(image: logoToUse!)
let stackView = UIStackView(arrangedSubviews: [label, imageView])
stackView.axis = .vertical
stackView.distribution = .fillProportionally
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalToConstant: 50), // this gets ignored
stackView.topAnchor.constraint(equalTo: self.view.topAnchor),
stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
view.heightAnchor.constraint(equalTo: stackView.heightAnchor)
])
}
}
This is how it looks in the simulator (going from border to border):
Question: Why is the UIImageView ignoring my widthAnchor constraint of 50pt, and why does the aspect ratio of the original image get changed? How can I constrain the UIImage (or the UIImageView) to e.g. half the screen width and maintain the aspect ratio?
By default, a vertical stack view has .alignment = .fill ... so it will stretch the arranged subviews to "fill the width of the stack view."
Change it to:
stackView.alignment = .center
As a side note, get rid of the stackView.distribution = .fillProportionally ... it almost certainly is not what you want.

How to center two views in super view with greater than or equal to constraints

I made an example ViewController with two Labels to highlight my issue. The goal is to vertically separate the labels by 10, and then center them vertically using greater than or equal to constraints. I'm using visual format, but this should apply if I setup my constraints like view.topAnchor.constraint(greaterThan.... I also have two constraints to horizontally layout the labels
My ViewController:
class myVC: UIViewController {
lazy var titleLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "Hello World"
l.font = .systemFont(ofSize: 50)
l.textColor = .black
return l
}()
lazy var descLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "description"
l.font = .systemFont(ofSize: 35)
l.textColor = .gray
return l
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
view.addSubview(titleLabel)
view.addSubview(descLabel)
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
descLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor).isActive = true
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-(<=50)-[titleLabel]-(10)-[descLabel]-(<=50)-|", options: .init(), metrics: nil, views: ["titleLabel": titleLabel, "descLabel": descLabel]))
}
}
This results in . From my understanding, this SHOULD separate the views by 10 pts, and center the labels vertically because in the format "V:|-(<=50)-[titleLabel]-(10)-[descLabel]-(<=50)-|" I say that the distance between the Title Label's top and the superView's top should be at least (greaterThanOrEqualTo) 50, and the distance between the description Label's bottom and the superView's bottom should be at least 50. What should my top and bottom constraints look like if I want to center the two labels vertically?
Yes, I realize I can just set vertical and horizontal centers, but this is an example I made for a problem I can't use those for. I need to be able to center the View with greater(or less) than or equal to constraints.
It's very difficult to center elements using VFL.
It's also difficult to center two elements unless they are embedded in a UIView or a UIStackView.
Here is one option by embedding the labels in a "container" UIView:
class MyVC: UIViewController {
lazy var titleLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "Hello World"
l.font = .systemFont(ofSize: 50)
l.textColor = .black
// center the text in the label - change to .left if desired
l.textAlignment = .center
return l
}()
lazy var descLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "description"
l.font = .systemFont(ofSize: 35)
l.textColor = .gray
// center the text in the label - change to .left if desired
l.textAlignment = .center
return l
}()
lazy var containerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
// give the labels and containerView background colors to make it easy to see the layout
titleLabel.backgroundColor = .green
descLabel.backgroundColor = .cyan
containerView.backgroundColor = .blue
// add containerView to view
view.addSubview(containerView)
// add labels to containerView
containerView.addSubview(titleLabel)
containerView.addSubview(descLabel)
NSLayoutConstraint.activate([
// constrain titleLabel Top to containerView Top
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor),
// constrain titleLabel Leading and Trailing to containerView Leading and Trailing
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
// constrain descLabel Leading and Trailing to containerView Leading and Trailing
descLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
descLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
// constrain descLabel Bottom to containerView Bottom
descLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
// constrain descLabel Top 10-pts from titleLabel Bottom
descLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10.0),
// constrain containerView centered horizontally and vertically
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
Result:
This can be achieved easily by using stackview. Add both the labels in stackview and center it vertically in the superview with all other constraints(top, leading, bottom, trailing).
Here is the sample code of view controller for your use-case.
class ViewController: UIViewController {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello \nWorld"
label.font = .systemFont(ofSize: 50)
label.backgroundColor = .orange
label.numberOfLines = 0
label.textColor = .black
return label
}()
lazy var descLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "a\n b\n c\n"
label.font = .systemFont(ofSize: 35)
label.backgroundColor = .green
label.numberOfLines = 0
label.textColor = .gray
return label
}()
lazy var contentView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 10
stackView.distribution = .fill
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
contentView.addArrangedSubview(titleLabel)
contentView.addArrangedSubview(descLabel)
self.view.addSubview(contentView)
let constraints = [
contentView.topAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.topAnchor),
contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
contentView.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor)
]
NSLayoutConstraint.activate(constraints)
}
}
The above code will result this view and it goes on to take the top and buttom space until it meets the safeArea. Moreover you can set the vertical content hugging and compression resistance priority to control which label to expand or shrink.

UIStackView alignment issue

I want to achieve this requirement
if vertical stack has two label than Text should be centre aligned to image
and if not than top aligned to Image
How can I achieve this without writing any code
You'll need to control the alignment of the outer (i.e final stack view which contains both the image and the labels' stack view) stack view.
As you will need to control which labels need to be added to the labels' stack view, I assume you will be doing this programmatically. So basically you'll need:
finalStackView.alignment = labelsStackView.arrangedSubviews.count > 2 ? .top : .center
Here is a complete example which produces the below outputs:
class ViewController: UIViewController {
let finalStackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
imageView.image = #imageLiteral(resourceName: "taylor-swift")
let label1 = UILabel()
let label2 = UILabel()
let label3 = UILabel()
let label4 = UILabel()
label1.translatesAutoresizingMaskIntoConstraints = false
label2.translatesAutoresizingMaskIntoConstraints = false
label3.translatesAutoresizingMaskIntoConstraints = false
label4.translatesAutoresizingMaskIntoConstraints = false
label1.text = "Hello"
label2.text = "72 mins"
label3.text = "Hello 3"
label4.text = "Hello 4"
let labelsStackView = UIStackView(arrangedSubviews: [label1, label2, label3, label4])
labelsStackView.translatesAutoresizingMaskIntoConstraints = false
labelsStackView.axis = .vertical
labelsStackView.distribution = .fill
labelsStackView.alignment = .leading
finalStackView.addArrangedSubview(imageView)
finalStackView.addArrangedSubview(labelsStackView)
finalStackView.axis = .horizontal
finalStackView.distribution = .fill
finalStackView.alignment = labelsStackView.arrangedSubviews.count > 2 ? .top : .center
view.addSubview(finalStackView)
finalStackView.translatesAutoresizingMaskIntoConstraints = false
finalStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
finalStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
finalStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
With the exact code above the output is:
With 2 labels added to the labelsStackView, the output is:
Keep the labels vertical stackview in a Horizontal stackView.
if you have more than 2 labels, change Horizontal stackView alignment to top else keep it to center.
Your layout structure be like
> main stack view (Horizontal)
> Image
> stack view (Horizontal)
>labels stack view (Vertical)
> Labels

How to make a horizontal StackView to have the first element's width and fill the rest of it

I'm new with swift and trying to create an input field at the moment. My problem is, that I would like to have a Label as shown in the picture:
So far, I'm working with StackViews: One vertical one for the input fields, and three horizontal ones to have the Title and the user input. My code so far is as follows:
// Initialize outter stackview
let feedbackOutterSV = UIStackView()
view.addSubview(feedbackOutterSV)
feedbackOutterSV.translatesAutoresizingMaskIntoConstraints = false
feedbackOutterSV.axis = NSLayoutConstraint.Axis.vertical
NSLayoutConstraint.activate([
feedbackOutterSV.topAnchor.constraint(equalTo: tutorialText.bottomAnchor, constant: 10),
feedbackOutterSV.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
feedbackOutterSV.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
feedbackOutterSV.heightAnchor.constraint(equalToConstant: 300)
])
// Initalize inner stackview for title
let feedbackInnerSVTitle = UIStackView()
feedbackOutterSV.addArrangedSubview(feedbackInnerSVTitle)
feedbackInnerSVTitle.translatesAutoresizingMaskIntoConstraints = false
feedbackInnerSVTitle.axis = .horizontal
feedbackInnerSVTitle.alignment = .fill
feedbackInnerSVTitle.distribution = .fillProportionally
let titleLabel = UILabel()
feedbackInnerSVTitle.addArrangedSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.text = "feedback.input.title".localize()
titleLabel.font = UIFont.preferredFont(forTextStyle: .body)
titleLabel.textColor = .gray
let titleTextView = UITextView()
feedbackInnerSVTitle.addArrangedSubview(titleTextView)
titleTextView.translatesAutoresizingMaskIntoConstraints = false
titleTextView.font = UIFont.preferredFont(forTextStyle: .body)
titleTextView.isScrollEnabled = false
NSLayoutConstraint.activate([
titleLabel.widthAnchor.constraint(equalToConstant: 39)
])
This code gives the expected output for English, however I have to implement it in different languages, so I can't use a constant width.
Can anyone tell me how to change my code, so I don't need the constant constraint but the width of the Label is adjusted to the length of the word?
Thanks in advance
Couple things...
I assume you want the "title label" to be top-aligned with your textView, so change .fill to .top:
feedbackInnerSVTitle.alignment = .top // .fill
and, don't use .fillProportionally
feedbackInnerSVTitle.distribution = .fill // .fillProportionally
Now, you'll likely see each element taking 50% of the width, so change the content hugging priority for your title label:
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
and, finally, don't set a width constraint on your title label:
// NSLayoutConstraint.activate([
// titleLabel.widthAnchor.constraint(equalToConstant: 39)
// ])
Result:
In your code, width constraint on titleLabel must be set to titleLabel.intrinsicContentSize.width
NSLayoutConstraint.activate([
titleLabel.widthAnchor.constraint(equalToConstant: titleLabel.intrinsicContentSize.width)
])
Also, set the distribution of feedbackInnerSVTitle as .fill
feedbackInnerSVTitle.distribution = .fill
I think you could use NSLayoutConstraint.activate([
titleLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0)] to let it grow depending on the content

Add UILabel over UIImage inside StackView

The part of my storyboard looks like on first photo. On second photo I present constraints. What I am trying to achieve is to put UILabel at the bottom over the UIImageView (with photo "DJI_0049").
Add the label to the same view as the UIStackView.. such that they are siblings/adjacent views. Add the imageView to the stackView and then constrain the label to the imageView.
Since they are siblings it WILL work. Do note that the label CANNOT be an arranged sub-view of the stackView.. otherwise broken constraints will be the result.
Example:
//
// ViewController.swift
// TestSO
//
// Created by Brandon on 2018-02-28.
// Copyright © 2018 XIO. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
let imageView = UIImageView()
let otherView = UIView()
let otherView2 = UIView()
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .fill
self.view.addSubview(stackView)
self.view.addSubview(label)
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(otherView)
stackView.addArrangedSubview(otherView2)
label.text = "Hello World"
label.textColor = UIColor.white
imageView.backgroundColor = UIColor.purple
otherView.backgroundColor = UIColor.red
otherView2.backgroundColor = UIColor.blue
stackView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor),
stackView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor),
stackView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor)
])
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
label.leftAnchor.constraint(greaterThanOrEqualTo: imageView.leftAnchor),
label.rightAnchor.constraint(lessThanOrEqualTo: imageView.rightAnchor),
label.topAnchor.constraint(greaterThanOrEqualTo: imageView.topAnchor),
label.bottomAnchor.constraint(lessThanOrEqualTo: imageView.bottomAnchor)
])
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: 100.0),
otherView.heightAnchor.constraint(equalToConstant: 100.0),
otherView2.heightAnchor.constraint(equalToConstant: 100.0)
])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You can’t overlap views like this in a stack view.
Your best option is to use normal auto layout constraints to lay out one of the views (probably the image) and then either use a stack view or constraints to layout the views that you want over it.
With a UIStackView this will not work (for as far I know UIStackView?!). But it can be easily achieved with the following constraints:
Set leading, top and trailing edges from the UIImageView to his superview. Define some height for the UIImageView.
Secondly, set leading and trailing edges from the UILabel to the UIImageView. Make sure the UILabel and UIImageView share the same superview.
Lastly, set the bottom of the UILabel to the bottom of the UIImageView.
i was also able to do this in a stackview by adding the label as a subview to the image view.
cell.avatarImage.leadingAnchor.constraint(equalTo: cell.cellBodyStackView.leadingAnchor, constant: 10).isActive = true
cell.avatarImage.topAnchor.constraint(equalTo: cell.cellBodyStackView.topAnchor).isActive = true
cell.avatarImage.bottomAnchor.constraint(equalTo: cell.cellBodyStackView.bottomAnchor).isActive = true
cell.avatarImage.trailingAnchor.constraint(equalTo: cell.commentLabel.leadingAnchor, constant: -10).isActive = true
cell.avatarImage.widthAnchor.constraint(lessThanOrEqualToConstant: cellWidth * 0.25).isActive = true
cell.avatarImage.addSubview(cell.usernameLabel)
cell.usernameLabel.trailingAnchor.constraint(equalTo: cell.avatarImage.trailingAnchor).isActive = true
cell.usernameLabel.leadingAnchor.constraint(equalTo: cell.avatarImage.leadingAnchor).isActive = true
cell.usernameLabel.bottomAnchor.constraint(equalTo: cell.avatarImage.bottomAnchor).isActive = true
cell.usernameLabel.centerXAnchor.constraint(equalTo: cell.avatarImage.centerXAnchor).isActive = true
cell.usernameLabel.textAlignment = .center
cell.usernameLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5)
cell.usernameLabel.textColor = UIColor.white

Resources