UIStackView and truncated Multiline UILabels - ios

I want to add several multiline Labels to an UIStackView.
But I always end up my Labels being truncated. As seen in this Screenshot
But I like to have it more as shown here (my faked Screenshot)
Here is my Code. First I create the parent/master StackView, put it into an ScrollView (which is tucked to the screen)
stackView = UIStackView()
stackView.axis = .Vertical
stackView.distribution = .Fill
stackView.spacing = 2
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
NSLayoutConstraint.activateConstraints(stackConstraints)
let s1 = createHeaderStackView()
stackView.insertArrangedSubview(s1, atIndex: 0)
let lbl2 = makeLabel()
lbl2.text = "Second One"
stackView.insertArrangedSubview(lbl2, atIndex: 1)
scrollView.setNeedsLayout()
while makeLabel and makeButton are just helper functions
func makeButton() -> UIButton {
let btn = UIButton(type: .Custom)
btn.backgroundColor = UIColor.lightGrayColor()
return btn
}
func makeLabel() -> UILabel {
let lbl = UILabel()
lbl.font = UIFont.systemFontOfSize(18)
lbl.setContentCompressionResistancePriority(1000, forAxis: .Vertical)
lbl.setContentHuggingPriority(10, forAxis: .Vertical)
lbl.preferredMaxLayoutWidth = scrollView.frame.width
lbl.numberOfLines = 0
lbl.textColor = UIColor.blackColor()
lbl.backgroundColor = UIColor.redColor()
return lbl
}
The createHeaderStackViewmethod is to configure my StackView to put inside a StackView with all my header stuff.
func createHeaderStackView() -> UIStackView {
let lblHeader = makeLabel()
lblHeader.text = "UIStackView"
lblHeader.textAlignment = .Center
let lblInfo = makeLabel()
lblInfo.text = "This is a long text, over several Lines. Because why not and am able to to so, unfortunaltey Stackview thinks I'm not allowed."
lblInfo.textAlignment = .Natural
lblInfo.layoutIfNeeded()
let lblInfo2 = makeLabel()
lblInfo2.text = "This is a seconds long text, over several Lines. Because why not and am able to to so, unfortunaltey Stackview thinks I'm not allowed."
lblInfo2.textAlignment = .Natural
lblInfo2.layoutIfNeeded()
let btnPortal = makeButton()
btnPortal.setTitle("My Button", forState: .Normal)
btnPortal.addTarget(self, action: "gotoPushWebPortalAction", forControlEvents: .TouchUpInside)
let headerStackView = UIStackView(arrangedSubviews: [lblHeader, btnPortal, lblInfo, lblInfo2])
headerStackView.axis = .Vertical
headerStackView.alignment = .Center
headerStackView.distribution = .Fill
headerStackView.spacing = 2
headerStackView.setContentCompressionResistancePriority(1000, forAxis: .Vertical)
headerStackView.setContentHuggingPriority(10, forAxis: .Vertical)
headerStackView.setNeedsUpdateConstraints()
headerStackView.setNeedsLayout()
//headerStackView.layoutMarginsRelativeArrangement = true
return headerStackView
}
so to make a long story short: What is needed to adjust my stackviews, so each stackview and therefore label is shown in full glorious size? I tried to compress and hug everything, but it didn't seem to work. And googling uistackview uilabel multiline truncated seems to be a dead end, too
I appreciate any help,
regards Flori

You have to specify the dimensions of the stack view. The label will not "overflow" into the next line if the dimensions of the stack view is ambiguous.
This code is not exactly the output you'd want, but you'll get the idea:
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView()
stackView.axis = .Vertical
stackView.distribution = .Fill
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
let views = ["stackView" : stackView]
let h = NSLayoutConstraint.constraintsWithVisualFormat("H:|-50-[stackView]-50-|", options: [], metrics: nil, views: views)
let w = NSLayoutConstraint.constraintsWithVisualFormat("V:|-100-[stackView]-50-|", options: [], metrics: nil, views: views)
view.addConstraints(h)
view.addConstraints(w)
let lbl = UILabel()
lbl.preferredMaxLayoutWidth = stackView.frame.width
lbl.numberOfLines = 0
lbl.text = "asddf jk;v ijdor vlb otid jkd;io dfbi djior dijt ioure f i;or dfuu;nfg ior mf;drt asddf jk;v ijdor vlb otid jkd;io dfbi djior dijt ioure f infg ior mf;drt asddf jk;v ijdor vlb otid jkd;io dfbi djior dijt ioure f i;or dfuu;nfg ior mf;drt "
dispatch_async(dispatch_get_main_queue(), {
stackView.insertArrangedSubview(lbl, atIndex: 0)
})
}

As per I know If you are using this inside UITableViewCell then each rotation you have to reload tableView.
You can use stackView.distribution = .fillProportionally it will work fine.

You should try this on storyboard.
make the stackview height as equal to 60% or 70% of your view.
Make the multiplier as 0.6 or 0.7.

Related

Center text vertically in line of NSMutableAttributedString

I'm using NSMutableAttributedString to setup text style. When I set .minimumLineHeight, it centre text in line to the bottom of line. I would like to customise somehow this alignment to centre text in line vertically.
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = fontLineHeight // 24 for example
attributedText.addAttribute(.paragraphStyle, value: paragraphStyle, range: fullRange)
I want to get such result:
The problem with providing a lineHeight is that you override the default height-calculation-behaviour with a hardcoded value. Even if you've provided the font's line height as the value, it can vary dynamically based on provided content and it's best to leave this to auto layout (Refer https://stackoverflow.com/a/33278748/9293498)
Auto-layout has a solution for these issues most of the time. All you need is a proper constraint setup for your label (and lineSpacing in your scenario) which can enable it to scale automatically based on provided text with just-enough space required. Both NSAttributedString and String values should work
Here's a code sample I've written trying to simulate your requirement:
Views:
private let termsAndConditionsContainer: UIStackView = {
let container = UIStackView()
container.backgroundColor = .clear
container.spacing = 16
container.axis = .vertical
container.alignment = .leading
container.distribution = .fill
container.translatesAutoresizingMaskIntoConstraints = false
return container
}()
private let dataAgreementButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(), for: .normal)
button.layer.borderColor = UIColor.gray.cgColor
button.layer.borderWidth = 0.5
button.layer.cornerRadius = 16
return button
}()
private let dataAgreementLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 17, weight: .medium)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let tosAgreementButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(), for: .normal)
button.layer.borderColor = UIColor.gray.cgColor
button.layer.borderWidth = 0.5
button.layer.cornerRadius = 16
return button
}()
private let tosAgreementLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 17, weight: .medium)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Functions:
private func attributedString(with text: String) -> NSAttributedString {
//Leave it to auto-layout to determine the line height
let attributedString = NSMutableAttributedString(string: text)
let style = NSMutableParagraphStyle()
style.lineSpacing = 8 // I've just declared the spacing between lines
attributedString.addAttribute(.paragraphStyle, value: style, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
private func generateRowContainer() -> UIStackView {
let container = UIStackView()
container.backgroundColor = .clear
container.spacing = 16
container.axis = .horizontal
container.alignment = .center
container.distribution = .fill
container.layer.borderWidth = 0.5
container.layer.borderColor = UIColor.green.cgColor
container.translatesAutoresizingMaskIntoConstraints = false
return container
}
And here's my constraint setup as I add the above views in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
let dataAgreementContainer = generateRowContainer()
dataAgreementContainer.addArrangedSubview(dataAgreementButton)
dataAgreementLabel.attributedText = attributedString(with: "I agree with the Information note regarding personal data processing")
dataAgreementContainer.addArrangedSubview(dataAgreementLabel)
termsAndConditionsContainer.addArrangedSubview(dataAgreementContainer)
let tosAgreementContainer = generateRowContainer()
tosAgreementContainer.addArrangedSubview(tosAgreementButton)
tosAgreementLabel.attributedText = attributedString(with: "I agree with terms and conditions")
tosAgreementContainer.addArrangedSubview(tosAgreementLabel)
termsAndConditionsContainer.addArrangedSubview(tosAgreementContainer)
view.addSubview(termsAndConditionsContainer)
NSLayoutConstraint.activate([
dataAgreementButton.widthAnchor.constraint(equalToConstant: 32),
dataAgreementButton.heightAnchor.constraint(equalToConstant: 32),
tosAgreementButton.widthAnchor.constraint(equalToConstant: 32),
tosAgreementButton.heightAnchor.constraint(equalToConstant: 32),
termsAndConditionsContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32),
termsAndConditionsContainer.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -32),
termsAndConditionsContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
I got the below output:
In the above example, I've used a horizontal stack view to maintain the alignment of text and radio button, which seems efficient to me for your requirement. But the point is, as long as your label is in a properly constrained environment, you don't have the need to manually specify any form of its height.

IOS| UILabel not visible in UIStack view

I am trying to build a vertical stack view with a UI label and multiple horizontal stack views, However, I only see the multiple horizontal stacks in the UI and not the UI label. Here is some code
final class FinalView: UIView {
let mainStackView = UIStackView()
init() {
super.init(frame: .zero)
let label: UILabel = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Sorry ?"
...
let verticalStackView = UIStackView(arrangedSubviews: multiplehorizontalViews)
verticalStackView.axis = .vertical
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.spacing = 5.0
mainStackView.addArrangedSubview(label)
mainStackView.addArrangedSubview(verticalStackView)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
mainStackView.axis = .vertical
}
}
I am only seeing verticalStackView but not label. When I removed this mainStackView.addArrangedSubview(verticalStackView) from the code, I can see the label. But When it is there I can't see the label. Any ideas on the issue or any tips for debugging?
Your code is working completely fine. Just checked on iOS 13 with below code
let mainStackView = UIStackView()
mainStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainStackView)
var vflString = "V:|-[mainStackView]-|"
var vflConstraints = NSLayoutConstraint.constraints(withVisualFormat: vflString, options: [], metrics: nil, views: ["mainStackView" : mainStackView])
view.addConstraints(vflConstraints)
vflString = "H:|-[mainStackView]-|"
vflConstraints = NSLayoutConstraint.constraints(withVisualFormat: vflString, options: [], metrics: nil, views: ["mainStackView" : mainStackView])
view.addConstraints(vflConstraints)
let label: UILabel = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Sorry ?"
let label1 = UILabel(frame: .zero)
label1.text = "Label 1"
let label2 = UILabel(frame: .zero)
label2.text = "Label 2"
let label3 = UILabel(frame: .zero)
label3.text = "Label 3"
let multiplehorizontalViews = [label1, label2, label3]
let verticalStackView = UIStackView(arrangedSubviews: multiplehorizontalViews)
verticalStackView.axis = .horizontal
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.spacing = 5.0
verticalStackView.distribution = .fillProportionally
mainStackView.addArrangedSubview(label)
mainStackView.addArrangedSubview(verticalStackView)
mainStackView.translatesAutoresizingMaskIntoConstraints = false
mainStackView.axis = .vertical
I'm pretty sure it's not your case but in mine
this applied:
UIBezierPath: roundedRect: byRoundingCorners: cornerRadii: acts weird
bezier path on layer was cutting all content from the stackview
you can check in the visual debugger what's going in: if the labels
text show up there then it's an issue with layer clipping

Embedd StackView in ScrollView that is embedded in a main StackView

Embedd StackView in ScrollView that is embedded in a main StackView
I am having trouble with a rather complicated detail view that I want to do programmatically. My view hierarchy looks something like this:
Since this might be better explained visualising, I have a screenshot here:
My problem is that I don't know how to set the height constraint on descriptionTextView – right now it's set to 400. What I want though is that it takes up all the space available as the middle item of the main stack view. Once one or more comments are added to the contentStackView, the text field should shrink.
I am not sure which constraints for which views I must set to achieve this...
Here's my take on it so far:
import UIKit
class DetailSampleViewController: UIViewController {
lazy var mainStackView: UIStackView = {
let m = UIStackView()
m.axis = .vertical
m.alignment = .fill
m.distribution = .fill
m.spacing = 10
m.translatesAutoresizingMaskIntoConstraints = false
m.addArrangedSubview(titleTextField)
m.addArrangedSubview(contentScrollView)
m.addArrangedSubview(footerStackView)
return m
}()
lazy var titleTextField: UITextField = {
let t = UITextField()
t.borderStyle = .roundedRect
t.placeholder = "Some Fancy Placeholder"
t.text = "Some Fancy Title"
t.translatesAutoresizingMaskIntoConstraints = false
return t
}()
lazy var contentScrollView: UIScrollView = {
let s = UIScrollView()
s.contentMode = .scaleToFill
s.keyboardDismissMode = .onDrag
s.translatesAutoresizingMaskIntoConstraints = false
s.addSubview(contentStackView)
return s
}()
lazy var contentStackView: UIStackView = {
let s = UIStackView()
s.translatesAutoresizingMaskIntoConstraints = false
s.axis = .vertical
s.alignment = .fill
s.distribution = .equalSpacing
s.spacing = 10
s.contentMode = .scaleToFill
s.addArrangedSubview(descriptionTextView)
s.addArrangedSubview(getCommentLabel(with: "Some fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "Another fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "And..."))
s.addArrangedSubview(getCommentLabel(with: "..even..."))
s.addArrangedSubview(getCommentLabel(with: "...more..."))
s.addArrangedSubview(getCommentLabel(with: "...comments..."))
s.addArrangedSubview(getCommentLabel(with: "Some fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "Another fancy comment"))
s.addArrangedSubview(getCommentLabel(with: "And..."))
s.addArrangedSubview(getCommentLabel(with: "..even..."))
s.addArrangedSubview(getCommentLabel(with: "...more..."))
s.addArrangedSubview(getCommentLabel(with: "...comments..."))
return s
}()
lazy var descriptionTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont.systemFont(ofSize: 17.0)
tv.clipsToBounds = true
tv.layer.cornerRadius = 5.0
tv.layer.borderWidth = 0.25
tv.translatesAutoresizingMaskIntoConstraints = false
tv.text = """
Some fancy textfield text,
spanning over multiple
lines
...
"""
return tv
}()
lazy var footerStackView: UIStackView = {
let f = UIStackView()
f.axis = .horizontal
f.alignment = .fill
f.distribution = .fillEqually
let commentLabel = UILabel()
commentLabel.text = "Comments"
let addCommentButton = UIButton(type: UIButton.ButtonType.system)
addCommentButton.setTitle("Add Comment", for: .normal)
f.addArrangedSubview(commentLabel)
f.addArrangedSubview(addCommentButton)
return f
}()
override func loadView() {
view = UIView()
view.backgroundColor = . systemBackground
navigationController?.isToolbarHidden = true
view.addSubview(mainStackView)
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12),
mainStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12),
mainStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
titleTextField.heightAnchor.constraint(equalToConstant: titleTextField.intrinsicContentSize.height),
contentStackView.leadingAnchor.constraint(equalTo: contentScrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: contentScrollView.trailingAnchor),
contentStackView.topAnchor.constraint(equalTo: contentScrollView.topAnchor),
contentStackView.bottomAnchor.constraint(equalTo: contentScrollView.bottomAnchor),
descriptionTextView.heightAnchor.constraint(equalToConstant: 400),
descriptionTextView.leadingAnchor.constraint(equalTo: mainStackView.leadingAnchor),
descriptionTextView.trailingAnchor.constraint(equalTo: mainStackView.trailingAnchor),
])
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Detail View"
}
func getCommentLabel(with text: String) -> UILabel {
let l = UILabel()
l.layer.borderWidth = 0.25
l.translatesAutoresizingMaskIntoConstraints = false
l.text = text
return l
}
}
You're close, but a couple notes:
When using stack views - particularly inside scroll views - you sometimes need to explicitly define which elements can be stretched or not, and which elements can be compressed or not.
To get the scroll view filled before it has enough content, you need to set constraints so the combined content height is equal to the scroll view frame's height, but give that constraint a low priority so auto-layout can "break" it when you have enough vertical content.
A personal preference: I'm generally not a fan of adding subviews inside lazy var declarations. It can become confusing when trying to setup constraints.
I've re-worked your posted code to at least get close to what you're going for. It starts with NO comment labels... tapping the "Add Comment" button will add "numbered comment labels" and every third comment will wrap onto multiple lines.
Not really all that much in the way of changes... and I think I added enough comments to make things clear.
class DetailSampleViewController: UIViewController {
lazy var mainStackView: UIStackView = {
let m = UIStackView()
m.axis = .vertical
m.alignment = .fill
m.distribution = .fill
m.spacing = 10
m.translatesAutoresizingMaskIntoConstraints = false
// don't add subviews here
return m
}()
lazy var titleTextField: UITextField = {
let t = UITextField()
t.borderStyle = .roundedRect
t.placeholder = "Some Fancy Placeholder"
t.text = "Some Fancy Title"
t.translatesAutoresizingMaskIntoConstraints = false
return t
}()
lazy var contentScrollView: UIScrollView = {
let s = UIScrollView()
s.contentMode = .scaleToFill
s.keyboardDismissMode = .onDrag
s.translatesAutoresizingMaskIntoConstraints = false
// don't add subviews here
return s
}()
lazy var contentStackView: UIStackView = {
let s = UIStackView()
s.translatesAutoresizingMaskIntoConstraints = false
s.axis = .vertical
s.alignment = .fill
// distribution needs to be .fill (not .equalSpacing)
s.distribution = .fill
s.spacing = 10
s.contentMode = .scaleToFill
// don't add subviews here
return s
}()
lazy var descriptionTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont.systemFont(ofSize: 17.0)
tv.clipsToBounds = true
tv.layer.cornerRadius = 5.0
tv.layer.borderWidth = 0.25
tv.translatesAutoresizingMaskIntoConstraints = false
tv.text = """
Some fancy textfield text,
spanning over multiple lines.
This textView now has a minimum height of 160-pts.
"""
return tv
}()
lazy var footerStackView: UIStackView = {
let f = UIStackView()
f.axis = .horizontal
f.alignment = .fill
f.distribution = .fillEqually
let commentLabel = UILabel()
commentLabel.text = "Comments"
let addCommentButton = UIButton(type: UIButton.ButtonType.system)
addCommentButton.setTitle("Add Comment", for: .normal)
// add a target so we can add comment labels
addCommentButton.addTarget(self, action: #selector(addCommentLabel(_:)), for: .touchUpInside)
// don't allow button height to be compressed
addCommentButton.setContentCompressionResistancePriority(.required, for: .vertical)
f.addArrangedSubview(commentLabel)
f.addArrangedSubview(addCommentButton)
return f
}()
// just for demo - numbers the added comment labels
var commentIndex: Int = 0
// do all this in viewDidLoad(), not in loadView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = . systemBackground
navigationController?.isToolbarHidden = true
title = "Detail View"
// add the mainStackView
view.addSubview(mainStackView)
// add elements to mainStackView
mainStackView.addArrangedSubview(titleTextField)
mainStackView.addArrangedSubview(contentScrollView)
mainStackView.addArrangedSubview(footerStackView)
// add contentStackView to contentScrollView
contentScrollView.addSubview(contentStackView)
// add descriptionTextView to contentStackView
contentStackView.addArrangedSubview(descriptionTextView)
// tell contentStackView to be the height of contentScrollView frame
let contentStackHeight = contentStackView.heightAnchor.constraint(equalTo: contentScrollView.frameLayoutGuide.heightAnchor)
// but give it a lower priority do it can grow as comment labels are added
contentStackHeight.priority = .defaultLow
NSLayoutConstraint.activate([
// constrain mainStackView top / bottom / leading / trailing to safe area
mainStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12),
mainStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12),
mainStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
// title text field
titleTextField.heightAnchor.constraint(equalToConstant: titleTextField.intrinsicContentSize.height),
// minimum height for descriptionTextView
descriptionTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 160.0),
// constrain contentStackView top / leading / trailing / bottom to contentScrollView
contentStackView.topAnchor.constraint(equalTo: contentScrollView.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: contentScrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: contentScrollView.trailingAnchor),
contentStackView.bottomAnchor.constraint(equalTo: contentScrollView.bottomAnchor),
// constrain contentStackView width to contentScrollView frame
contentStackView.widthAnchor.constraint(equalTo: contentScrollView.frameLayoutGuide.widthAnchor),
// activate contentStackHeight constraint
contentStackHeight,
])
// during dev, give some background colors so we can see the frames
contentScrollView.backgroundColor = .cyan
descriptionTextView.backgroundColor = .yellow
}
#objc func addCommentLabel(_ sender: Any?) -> Void {
// commentIndex is just used to number the added comments
commentIndex += 1
// let's make every third label end up with multiple lines, just to
// confirm variable-height labels won't mess things up
var s = "This is label \(commentIndex)"
if commentIndex % 3 == 0 {
s += ", and it has enough text that it should need to wrap onto multiple lines, even in landscape orientation."
}
let v = getCommentLabel(with: s)
// don't let comment labels stretch vertically
v.setContentHuggingPriority(.required, for: .vertical)
// don't let comment labels get compressed vertically
v.setContentCompressionResistancePriority(.required, for: .vertical)
contentStackView.addArrangedSubview(v)
// auto-scroll to bottom to show newly added comment label
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
let r = CGRect(x: 0.0, y: self.contentScrollView.contentSize.height - 1.0, width: 1.0, height: 1.0)
self.contentScrollView.scrollRectToVisible(r, animated: true)
}
}
func getCommentLabel(with text: String) -> UILabel {
let l = UILabel()
l.layer.borderWidth = 0.25
l.translatesAutoresizingMaskIntoConstraints = false
l.text = text
// allow wrapping / multi-line comments
l.numberOfLines = 0
return l
}
}

Choose which subview on a stackView will stretch (Programmatically)

I have a display of horizontal stack views, which subviews consists on a label and a textField. the stackView is constrained with the borders of the view
I'm trying to stretch my textField subview to so it fills the remaining space of the stack, while the label's stacks adjusts to fit the label size itself. But the inverse is happening. I've tried many solutions but nothing helped me. All the views and constraints we're made programmatically.
For my stack, I'm using:
func customTextField() -> UIStackView {
let stack: UIStackView = {
let sv = UIStackView()
sv.axis = .horizontal
sv.isLayoutMarginsRelativeArrangement = true
sv.alignment = .leading
sv.backgroundColor = .red
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
let label: UILabel = {
let lb = UILabel()
lb.backgroundColor = .red
lb.text = "Label is here"
lb.translatesAutoresizingMaskIntoConstraints = false
return lb
}()
let textField: UITextField = {
let tf = UITextField()
tf.backgroundColor = .blue
tf.text = "Text Field"
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
stack.addArrangedSubview(label)
stack.addArrangedSubview(textField)
return stack
}
the caller of my customTextField:
let profileUserStack: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 8
stack.contentMode = .scaleToFill
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
self.addSubview(profileUserStack)
for i in profileTextViews {
let view = self.customTextField()
profileUserStack.addArrangedSubview(view)
}
constraints.append(profileUserStack.buildConstraint(toItem: perfilImageView, constant: 32, type: .top, baseItem: .bottom))
constraints.append(profileUserStack.buildConstraint(toItem: self, constant: 16, type: .leading, baseItem: .leading))
constraints.append(profileUserStack.buildConstraint(toItem: self, constant: -16, type: .trailing, baseItem: .trailing))
activateConstraints(&constraints, to: self)
The results:
https://imgur.com/a/ccFZlRM
Notice that's exactly what I want to achieve. But I want the textField to be stretched.
Set contentHuggingPriority to your label such that it always stays as the size of its content and textField takes remaining space.
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)

UIStackView adjust height to subviews

I want the UIStackView to resize to fit their subviews (UIImageView and UILabel in this case)
let headerView = UIStackView()
headerView.axis = .vertical
headerView.alignment = .center
headerView.distribution = .equalSpacing
headerView.spacing = 10
let headerImage = UIImageView(...)
headerImage.contentMode = .scaleAspectFill
headerImage.clipsToBounds = true
headerImage.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: tableView.frame.width / 1.618)
let desciptionView = UILabel()
desciptionView.text = "Some very long text wrapping multiple lines..."
desciptionView.numberOfLines = 0
desciptionView.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
headerView.addArrangedSubview(headerImage)
headerView.addArrangedSubview(desciptionView)
print(headerView.bounds) // always 0,0,0,0
print(headerView.frame) // always 0,0,0,0
tableView.tableHeaderView = headerView
(in this code height and width are 0)
How to implement the wanted behaviour?
The only way I have found to do this is by setting the content hugging and compression resistance priorities of the child views to be required, something like this:
let arrangedViews = [filterLabel, image]
.map { (view: UIView) -> UIView in
view.setContentHuggingPriority(.required, for: .horizontal)
view.setContentCompressionResistancePriority(.required, for: .horizontal)
return view }
let stack = UIStackView(arrangedSubviews: arrangedViews)
stack.axis = .horizontal
stack.distribution = .fill
stack.spacing = 8
With those set on the arranged subviews, do you do indeed (or at least I did) get the desired result.

Resources