Multiline label in UIStackView
The correct answer is here:
https://stackoverflow.com/a/43110590/566360
- Embed the
UILabel
inside aUIView
(Editor -> Embed In -> View) - Use constraints to fit the
UILabel
to theUIView
(for example, trailing space, top space, and leading space to superview constraints)
The UIStackView
will stretch out the UIView
to fit properly, and the UIView
will constrain the UILabel
to multiple lines.
Horizontal UIStackView with two label (one multiline label, one one-line)
To try and simplify...
Forget calculating any widths... what matters is the horizontal Content Hugging
and Content Compression Resistance
Leave the left (blue) label at the defaults:
Content Hugging Priority
Horizontal: 251
Content Compression Resistance Priority:
Horizontal: 750
But set the right (orange) label to:
Content Hugging Priority
Horizontal: 1000
Content Compression Resistance Priority:
Horizontal: 1000
Results:
The only other issue would be if the text in the right-side label exceeds the full width of the view -- but you haven't indicated that you might need that much text.
StackView horizontally wrap content
Have you tried changing the alignment of stackViewAir and stackViewSleep to .center? I think that will do the trick here. Thanks
How to make horizontal UIStackView wrap height content with UIImageView as its children?
You cannot get this result with only Storyboard setup -- you will need some code.
Based on your image showing your desired output, you want each "row" to:
- have 3 images
- show them at original aspect-ratio
- small spacing between images (such as 4-pts)
- fit so the widths and heights maintain the ratios
First comment - forget Distribution = Fill Proportionally
on the stack views.
For this layout, the stack view should be:
Axis: Horizontal
Alignment: Fill
Distribution: Fill
Spacing: 4
So, what we need to do is constrain each imageView's aspect ratio based on its image's aspect ratio (height / width).
Here is a full example... it uses these 6 images:
We'll also embed the horizontal stack view(s) in a vertical stack view, using the same settings (expect Axis: Vertical
).
class AdjustStackViewController: UIViewController {
var vStack = UIStackView()
var picNames: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// six images, named "p1" - "p6"
for i in 1...6 {
picNames.append("p\(i)")
}
// vertical stack view
vStack.axis = .vertical
vStack.spacing = 4
// use auto-layout
vStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(vStack)
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain vStack
// Top + 20
// Leading and Trailing 0
vStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
vStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
vStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
])
fillStacks()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// re-fill images in new (shuffled) order
fillStacks()
}
func fillStacks() -> Void {
// remove existing horizontal stack views (if needed)
vStack.arrangedSubviews.forEach { v in
v.removeFromSuperview()
}
let shuffledImages = picNames.shuffled()
// two horizontal stack views each with 3 images
var picNum: Int = 0
for _ in 1...2 {
let hStack = UIStackView()
hStack.spacing = 4
vStack.addArrangedSubview(hStack)
for _ in 1...3 {
// make sure we can load the images
guard let img = UIImage(named: shuffledImages[picNum]) else {
fatalError("Could not load images!")
}
// create Image View
let imgView = UIImageView()
// set the image
imgView.image = img
// proportional constraint based on image dimensions
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: img.size.height / img.size.width).isActive = true
// add to hStack
hStack.addArrangedSubview(imgView)
// increment pic number
picNum += 1
}
}
}
}
Here's how it looks:
When you run this, each time you tap the view it will shuffle the order of the images, so you can see how the layout adapts.
iOS: UILabel multi-line text get truncated in horizontal UIStackView
alignment
/* The layout of the arrangedSubviews transverse to the axis;
e.g., leading/trailing edges in a vertical stack
*/
@property(nonatomic) UIStackViewAlignment alignment;
For .fill
StackView will automatically generate these layout attributes
for you.
imageView.bottom == label.bottom
imageView.top == label.top
the size of stackView depend on the 'voice' image
's size or size layout attributes of imageView
as it is fixed instead of adjustable UILable
which namely have the low priority.
For .center
StackView achieve a decent height by look for the max height between your label
and imageView
.
_UILayoutSpacer.bottom >= imageView.bottom
_UILayoutSpacer.bottom >= label.bottom
_UILayoutSpacer.top <= imageView.top
_UILayoutSpacer.top <= label.top
UIStackView distribution and alignment of a multiline UILabel
UPDATE
Since you're setting titleLabel.numberOfLines = 3
, one way to do this is simply to append three newlines to the title text. That will force titleLabel
to always consume its full height of three lines, forcing timeLabel
to the bottom.
That is, when you set titleLabel.text
, do it like this:
titleLabel.text = theTitle + "\n\n\n"
ORIGINAL
If you let one of the labels stretch vertically, the stretched label's text will be centered vertically within the stretched label's bounds, which is not what you want. So we can't let the labels stretch vertically. Therefore we need to introduce a padding view that can stretch but is otherwise invisible.
If the padding view gets squeezed down to zero height, the stack view will still put spacing before and after it, leading to double-spacing between titleLabel
and timeLabel
, which you also don't want.
So we'll need to implement all the spacing using padding views. Change verticalStackView.spacing
to 0.
Add a generic UIView
named padding1
to verticalStackView
after categoryLabel
, before titleLabel
. Constrain its height to equal 10.
Add a generic UIView
named padding2
to verticalStackView
after titleLabel
, before timeLabel
. Constrain its height to greater than or equal to 10 so that it can stretch.
Set the vertical hugging priorities of categoryLabel
, titleLabel
, and timeLabel
to required, so that they will not stretch vertically.
Constrain the height of verticalStackView
to the height of containerStackView
so that it will stretch one or more of its arranged subviews if needed to fill the vertical space available. The only arranged subview that can stretch is padding2
, so it will stretch, keeping the title text near the top and the time text at the bottom.
Also, constrain your containerStackView
to the bounds of contentView
and set containerStackView.translatesAutoresizingMaskIntoConstraints = false
.
Result:
Here's my playground:
import UIKit
import PlaygroundSupport
class MyCell: UICollectionViewCell {
var containerStackView: UIStackView!
var verticalStackView: UIStackView!
var categoryLabel: UILabel!
var titleLabel: UILabel!
var timeLabel: UILabel!
var imageView: UIImageView!
func createSubViews() {
// contains the UIStackview with the 3 labels and the UIImageView
containerStackView = UIStackView()
containerStackView.axis = .horizontal
containerStackView.distribution = .fill
containerStackView.alignment = .top
contentView.addSubview(containerStackView)
// the UIStackView for the labels
verticalStackView = UIStackView()
verticalStackView.axis = .vertical
verticalStackView.distribution = .fill
verticalStackView.spacing = 0
containerStackView.addArrangedSubview(verticalStackView)
categoryLabel = UILabel()
categoryLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
categoryLabel.textColor = UIColor.lightGray
verticalStackView.addArrangedSubview(categoryLabel)
let padding1 = UIView()
verticalStackView.addArrangedSubview(padding1)
titleLabel = UILabel()
titleLabel.numberOfLines = 3
titleLabel.lineBreakMode = .byWordWrapping
verticalStackView.addArrangedSubview(titleLabel)
let padding2 = UIView()
verticalStackView.addArrangedSubview(padding2)
timeLabel = UILabel()
timeLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
timeLabel.textColor = UIColor.lightGray
verticalStackView.addArrangedSubview(timeLabel)
// UIImageView
imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 5
layer.masksToBounds = true
containerStackView.addArrangedSubview(imageView)
categoryLabel.setContentHuggingPriority(.required, for: .vertical)
titleLabel.setContentHuggingPriority(.required, for: .vertical)
timeLabel.setContentHuggingPriority(.required, for: .vertical)
imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
containerStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(equalTo: containerStackView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor),
contentView.topAnchor.constraint(equalTo: containerStackView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
verticalStackView.heightAnchor.constraint(equalTo: containerStackView.heightAnchor),
padding1.heightAnchor.constraint(equalToConstant: 10),
padding2.heightAnchor.constraint(greaterThanOrEqualToConstant: 10),
])
}
}
let cell = MyCell(frame: CGRect(x: 0, y: 0, width: 320, height: 110))
cell.backgroundColor = .white
cell.createSubViews()
cell.categoryLabel.text = "MY CUSTOM LABEL"
cell.titleLabel.text = "This is my title"
cell.timeLabel.text = "3 days ago"
cell.imageView.image = UIGraphicsImageRenderer(size: CGSize(width: 110, height:110)).image { (context) in
UIColor.blue.set()
UIRectFill(.infinite)
}
PlaygroundPage.current.liveView = cell
UIStackView with wrap content width
Step 1: Drag a StackView add left, right, top/bottom constraint to it. (ignore error for now)
Step 2: Drag two labels and add it to stackView (continue to ignore warning)
Step 3: Now you need to resolve horizontal content ambiguity so change the content hugging priority of left label to 250 while leaving the right labels content hugging priority at 251
Step 4: Now comes your question of warp content for vertical height :) Simply select the right label and set its content compression resistance to 999 while leaving left label's content compression resistance at 751
If you are wondering whats happening with content compression resistance and content hugging priority and all, please read apple docs on the same for more detail
Just to give a gist of what am doing, in iOS UILabel
s have implicit content size, so they take size based on content. So is Stack View. Now because StackView has to deduce its height based on two labels it has and both of them have same content compression resistance and end up having different height in order to resolve confusion it looks for further assistance from you. You do that specifying that right label resists more to change in its implicit vertical height compared to left label.
That means now stack view knows that it has to consider right labels height to adjust its height while it can override the implicit size of left label :)
Confirmation of logic:
On changing right label font stack view height automatically increased and increased the height of left label as well :)
EDIT on Comment by OP:
As OP suggested in comment that conern is width and not height, step 4 is not necessary, your problem is resolved at the end of step 3. On setting content hugging priority it self
on changing right labels content UI updates as shown below
Thats all :) Hope that helps
Related Topics
Multiple Locations on Map (Using Mkmapitem and Clgeocoder)
How to Convert an Int to a Character in Swift
How to Send Multiple Parameterts to PHP Server in Http Post
How to Hide 'Back' Button on Navigation Bar on Iphone
Wkwebview and Nsurlprotocol Not Working
Styling the Cancel Button in a Uisearchbar
An Error Occurred Uploading to the Itunes Store
iOS How to Detect Programmatically When Top View Controller Is Popped
Play Mp3 Files with iPhone Sdk
Ios:Retrieve Rectangle Shaped Image from the Background Image
iPhone Different Screen Sizes in Flash? (Getting Black Bars)
Xcode: Failed to Get the Task for Process
Could Not Find Module for Target 'X86_64-Apple-Ios-Simulator'
Code Signing Is Required for Product Type Unit Test Bundle in Sdk iOS 8.0
Cocoapods Could Not Find Compatible Versions for Pod "Firebase/Core" | Cloud_Firestore, Flutter