UIImageView ignores widthAnchor inside UIStackView - ios

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.

Related

How to correctly add safe area inset to UIStackView?

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/

addSubview and addArrangedSubview not showing content

I'm adding a UILabel to a UIView and then adding the UIView to a UIStackView, but the UILabel is not showing.
override func viewDidLoad() {
super.viewDidLoad()
configureStackView()
label.text = "test"
containerView.addSubview(label)
stackView.addArrangedSubview(containerView)
view.addSubview(stackView)
setStackViewConstraints()
}
func configureStackView() {
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 5
}
func setStackViewConstraints() {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
I tried setting some sort of size for the view, but doesn't work:
containerView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
or setting constraints to the label to the containerView for widthAnchor, leadingAnchor, or trailingAnchor, but that doesn't work either.
UIView on its own does not have an intrinsicContentSize, which UIStackView uses to determine the size of the view when placed in the stackView. Either set constraints for the four edges of the label to the edges of the container
label.topAnchor.constraint(container.topAnchor, constant: myTopConstant).isActive = true
label.leading....
label.bottom....
label.trailing....
label.setContentHuggingPriority(to: .defaultLow, axis: .vertical)
(above, relax the contentHuggingPriority if you are not placing any other views in the stackView; since it has a fixed height and distribution = .fill, your stackView will try to stretch the arrangedViews to fit the height. If the label/container are the only candidates, you'll be breaking constraints with a .required contentHugging on the label)
or give the container a well-defined height (since the axis of your stackView is vertical)
containerView.heightAnchor.constraint(equalTo: ....).isActive = true
Also, in your container, set
translatesAutoresizing... = false

Can't get StackView to view items properly

I'm trying to add 4 items to a UIStackView, this 4 Items are all a simple square UIView, I added them all to a UIStackView but they won't stay square, it's like the UIStackView squeezes them or something. I tried setting the UIStackView to be the same height of the items, and set it's width to be the height of the items * 4 so I can try and get 1:1 ratio, but nothing worked for me.
The UIView is a simple UIView with background color. I tried to set it's widthAnchor and heightAnchor to 50, but I know the UIStackView has it's own way to size the items in it.
I don't really know what to do about this.
This is my UIStackView setup and constraints:
Setup:
private lazy var optionButtonStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [self.optionButton1, self.optionButton2, self.optionButton3, self.optionButton4])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.distribution = .fillEqually
stack.axis = .horizontal
stack.spacing = 2.5
return stack
}()
Constraints:
private func setupOptionButtonStack() {
addSubview(optionButtonStack)
NSLayoutConstraint.activate([
optionButtonStack.heightAnchor.constraint(equalToConstant: 50),
optionButtonStack.widthAnchor.constraint(equalToConstant: 200),
optionButtonStack.centerXAnchor.constraint(equalTo: self.centerXAnchor),
optionButtonStack.bottomAnchor.constraint(equalTo: buyNowButton.topAnchor, constant: -8),
])
}
This is the UIView in case this is needed:
private let optionButton1: UIView = {
let view = UIView()
view.backgroundColor = .appBlue
view.translatesAutoresizingMaskIntoConstraints = false
view.widthAnchor.constraint(equalToConstant: 50).isActive = true
view.heightAnchor.constraint(equalToConstant: 50).isActive = true
view.tag = 1
return view
}()
Give the button view a single constraint setting its width equal to its height:
view.widthAnchor.constraint(equalTo: view.heightAnchor).isActive = true
and set the stack view alignment at center.

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