UILabel text is getting cut off below bottom font line - ios

The bottom part of characters that extend below the baseline is cut off in a UILabel.
The '|' character is the easiest to notice this issue. See in the below screenshot that the '|' character extended well below the bottom of the comma in the 2nd line, but is then cut off in the 3rd line.
In the code I have:
let moreDetailsLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIHelper.HIRAGINO_SANS_FONT.withSize(30)
label.backgroundColor = UIColor.clear
label.text = "For more details|, check out our website|,"
label.textAlignment = .right
label.backgroundColor = UIColor.red
label.textColor = UIColor.white
label.numberOfLines = 0
return label
}()
I add it to the viewcontroller view's hierarchy:
view.addSubview(descriptionLabel)
then for the constraints, I have:
allConstraints += [moreDetailsLabel.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width/2)]
allConstraints += [moreDetailsLabel.leftAnchor.constraint(equalTo: moreDetailsContainerView.leftAnchor)]
allConstraints += [moreDetailsLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 130)
You will notice that I do not set a height constraint. I am hoping that I did not have to set that for UILabels because they will just shrink/stretch accordingly with the fixed width. I might be wrong in that believe, but it seems like that is the behavior.
The spacing between the top of the F and the bounding view seems to have a small off that I am hoping is the reason the text is cut off.

Related

Why does UIStackView not stack a UILabel arrangedSubview?

I have a UIStackView, and as arranged subviews, I have two UIViews, and a UILabel. The UIViews are stacked one after another, while the UILabel is aligned to the leading edge of the subview.
Code:
let stack = UIStackView()
stack.axis = .horizontal
stack.distribution = .fillProportionally
stack.alignment = .center
stack.spacing = 7
view.addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let icon = UIView()
icon.backgroundColor = UIColor.red
stack.addArrangedSubview(icon)
icon.translatesAutoresizingMaskIntoConstraints = false
icon.heightAnchor.constraint(equalToConstant: 42).isActive = true
icon.widthAnchor.constraint(equalToConstant: 42).isActive = true
let icon2 = UIView()
icon2.backgroundColor = UIColor.red
stack.addArrangedSubview(icon2)
icon2.translatesAutoresizingMaskIntoConstraints = false
icon2.heightAnchor.constraint(equalToConstant: 42).isActive = true
icon2.widthAnchor.constraint(equalToConstant: 42).isActive = true
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.sizeToFit()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .yellow
label.text = "Hello World! Again"
label.textColor = .black
stack.addArrangedSubview(label)
Output:
For clarity...
First, don't use .fillProportionally. Whatever you think that will do, it's wrong. You have explicitly set your two "icons" to be 42-pts wide each. If the stack view is using .fillProportionally, it will try to change the widths of those views, and you will get auto-layout conflicts.
Second, a UILabel with .numberOfLines = 0 must have a width. Otherwise, there is no way to know where to break the text... with a lot of text, it will extend way off the sides of the view.
Third, the line label.sizeToFit() isn't going to accomplish anything here. Just delete it.
If you add this line:
stack.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
then the stack view will expand to 200-pts wide...
Auto-layout will give the first arranged view a width of 42 (because that's what you declared it to be), and the same for the second view. Since you've set the spacing to 7, it will then calculate
42 + 7 + 42 + 7
which equals 98. It subtracts that from the stack view's width:
200 - 98 = 102
and that is the width it will give your label.
Result:
If you don't want to explicitly set the width to 200, you can set it to a percentage of the superview's width, or give it leading and trailing constraints.
Try to change distribution to fill
stack.distribution = .fill
With multiline set
After commenting it

UIStackView distribution and alignment of a multiline UILabel

I' struggling with some basic UIStackView distribution and alignment stuff.
I have an UICollectionViewCell which has a horizontal UIStackView at the contentView subview. This UIStackView has a vertical UIStackView for the three labels itself, and of course the UIImageView.
This is the code snippet for the screenshot below:
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 = 10.0
containerStackView.addArrangedSubview(verticalStackView)
categoryLabel = UILabel()
categoryLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
categoryLabel.textColor = UIColor.lightGray
verticalStackView.addArrangedSubview(categoryLabel)
titleLabel = UILabel()
titleLabel.numberOfLines = 3
titleLabel.lineBreakMode = .byWordWrapping
verticalStackView.addArrangedSubview(titleLabel)
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)
}
What I want to achive is, that the "time label" ("3 days ago") is always placed at the bottom of each UICollectionViewCell (aligned with the bottom of the UIImageView), regardless of the different title label lines.
I've played with various UIStackView distributions, constraining the "time label" and with the hugging priority of the "title label".
But anyhow I can't get it right. Any hints?
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
The problem is the vertical stack view. You apparently want to say: the middle label's top should hug the MyCustomLabel bottom, but the 3 Days Ago bottom should hug the overall bottom. That is not something you can say to a stack view.
And even if that is not what you want to say, you would still need to make the vertical stack view take on the full height of the cell, and how are you going to do that? In the code you showed, you don't do that at all; in fact, your stack view has zero size based on that code, which will lead to all sorts of issues.
I would suggest, therefore, that you just get rid of all the stack views and just configure the layout directly. Your layout is an easy one to configure using autolayout constraints.

How to truncate UILabel to show '...' at the end of the string

I have a UILabel() i am trying to truncate.
let nameLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 11)
label.numberOfLines = 0
//label.lineBreakMode = .byTruncatingTail
//label.adjustsFontSizeToFitWidth = false
label.textAlignment = .center
label.sizeToFit()
return label
}()
here are my constraints:
nameLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
nameLabel.widthAnchor.constraint(equalTo: self.widthAnchor, constant: -10).isActive = true
nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 8).isActive = true
The issue that i am having is that the actual string gets truncated but instead of having the string 'Johnson Rodriguez' truncate to ''Johnson Rod...'' it ends up cropping out anything after the space, resulting in the label displaying 'Johnson'
These are the things i have tried using:
label.lineBreakMode = .byTruncatingTail
label.sizeToFit()
label.preferredMaxLayoutWidth = 70
This is what i have seen other people use but for some reason i cant get it to display the ... instead of truncating the string at the first space.
Setting numberOfLines to 1 should do the trick for you.
From the docs:
This property controls the maximum number of lines to use in order to fit the label’s text into its bounding rectangle. The default value for this property is 1. To remove any maximum limit, and use as many lines as needed, set the value of this property to 0.

Swift/Cocoa Touch: How do I centre dynamically resized text in a UILabel?

I have a UILabel that takes the size of its parent view.
I want the label text to resize to fit within the containing view.
override func draw(_ rect: CGRect) {
let fontSize = self.frame.size.height * 0.9
var font = UIFont.preferredFont(forTextStyle: .body).withSize(fontSize)
font = UIFontMetrics(forTextStyle: .body).scaledFont(for: font)
let label = UILabel()
label.textAlignment = .center
label.font = font
label.frame.size = self.frame.size
label.minimumScaleFactor = 0.1
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = true
label.text = "5"
addSubview(label)
}
The above works just how I want. The number 5 is displayed in the centre of the containing view. However if I set the text to something longer (for example: label.text = "5"), its vertical position changes.
How do I ensure that, no matter the length of the string, the text will be centred vertically as well as horizontally.
Add this line of code in your UILabel set up. Should work as expected. This will make the label to center vertically.
label.baselineAdjustment = .alignCenters

The following label is truncated?? . Any suggestion how to display MultiLine UILabel?

UILabel does not wrap and shows more than one line.
According to the docs, my text should wrap on word boundary and be displayed in 2 lines. However, this does not occur. I'm using Swift 4 and the latest version of XCode.
let instruction = UILabel()
instruction.text = "Click And Touch Number To Make A Choice"
instruction.backgroundColor = .white
instruction.textColor = .black
instruction.font = UIFont.systemFont(ofSize: 20)
instruction.lineBreakMode = .byWordWrapping
instruction.textAlignment = .left
instruction.numberOfLines = 0
instruction.sizeToFit()
The label to wrap should have a frame whether with frame layout by setting width value or by auto layout by setting leading and trailing constraints that makes the label know it's boundaries and wrap when hitting the right most boundary to another line and so on according to it's content
let instruction = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100))
instruction.text = "Click And Touch Number To Make A Choice"
instruction.backgroundColor = .white
instruction.textColor = .black
instruction.font = UIFont.systemFont(ofSize: 20)
instruction.lineBreakMode = .byWordWrapping
instruction.textAlignment = .left
instruction.numberOfLines = 0
instruction.sizeToFit()
self.view.addSubview(instruction)
instruction.center = self.view.center
According to the docs:
In some cases, if a view does not have a superview, it may size itself to the screen bounds.
But in your case it doesn't. You just need to constrain label's width somehow and sizeToFit() wouldn't be even needed (Auto Layout will do its job) e.g.
instruction.translatesAutoresizingMaskIntoConstraints = false
instruction.widthAnchor.constraint(equalToConstant: 200.0).isActive = true

Resources