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

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.

Related

UILabel shows incorrect text in last line when text contains a line break and label has limited numberOfLines and lineBreakMode .byTruncatingTail

If you run the code snippet below, it will show incorrect text in the last line that has the ... ellipsis, as show in this image:
Once you change the lineBreakMode to byWordWrapping, or set the numberOfLines to 0, then the text is displayed correctly. Also, if you remove the line break /n from the text, then the text is displayed correctly.
So my question is: How can I use a /n line break in the text in a UILabel with a limited numberOfLines and lineBreakMode .byTruncatingTail without showing incorrect text on the last line?
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let text = "A line break challenge \nInvestigating a bug where the text in the last line is incorrect when numberOfLines is limited to for example 3. Very strange indeed. Peculiar. What to do about this? What am I missing?"
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byTruncatingTail //.byWordWrapping works, but not what I want.
let attributedString = NSAttributedString(string: text, attributes: [
.paragraphStyle: paragraphStyle
])
let label = UILabel()
label.attributedText = attributedString
label.numberOfLines = 3 //0 works, but is not what I want.
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 40.0).isActive = true
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20.0).isActive = true
label.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20.0).isActive = true
}
}

Fontsize of two labels on one line

So what I want: I'd like to have a label on one line. The first word should have the font size 100 and the other has the font size 10. Does anybody have an idea how to achieve this?
When I'm calling the method below I would get a string composed of both label with the exactly same properties.
public let speedLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
label.font = UIFont(name: "Helvetica-Bold", size: 100)
label.text = "0.0"
label.textColor = UIColor.blue
label.textAlignment = .left
return label
}()
public let speedUnitLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
label.font = UIFont(name: "Helvetica-Bold", size: 10)
label.text = "km/h"
label.textAlignment = .right
label.textColor = UIColor.blue
return label
}()
This is how I set up those labels:
func setupSpeedLabel() {
SpeedContainerView.addSubview(speedLabel)
speedLabel.centerXAnchor.constraint(equalTo: TopBackGroundView.centerXAnchor, constant: 0).isActive = true
speedLabel.centerYAnchor.constraint(equalTo: SpeedContainerView.centerYAnchor, constant: 0).isActive = true
speedLabel.isUserInteractionEnabled = false
view.bringSubviewToFront(speedLabel)
}
func setupSpeedUnitLabel() {
SpeedContainerView.addSubview(speedUnitLabel)
speedUnitLabel.centerXAnchor.constraint(equalTo: speedLabel.leftAnchor, constant: 0).isActive = true
speedUnitLabel.centerYAnchor.constraint(equalTo: SpeedContainerView.centerYAnchor, constant: 0).isActive = true
speedUnitLabel.isUserInteractionEnabled = false
view.bringSubviewToFront(speedUnitLabel)
}
Tried the code and labels had different properties, so the problem could be related to the parent view of the labels.
What I did though was to change speedUnitLabel.centerXAnchor.constraint on setupSpeedUnitLabel() from speedLabel.leftAnchor, constant: 0 to speedLabel.rightAnchor, constant: 20 (the 20 constant is just to give a little space in between text).
Ended up with this
func setupSpeedUnitLabel() {
SpeedContainerView.addSubview(speedUnitLabel)
speedUnitLabel.centerXAnchor.constraint(equalTo: speedLabel.rightAnchor, constant: 20).isActive = true
speedUnitLabel.centerYAnchor.constraint(equalTo: SpeedContainerView.centerYAnchor, constant: 0).isActive = true
speedUnitLabel.isUserInteractionEnabled = false
view.bringSubviewToFront(speedUnitLabel)
}
And this is the output:
enter image description here
What I assume is what you are trying to achieve.
Make sure SpeedContainerView and its parent view are big enough to support the 100 font size.
Hope this helps.

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

Why does resizing a label require a delay to update as expected when coming from NotificationCenter?

I'm in the process of adding dynamic type to my app and I'm trying to update the frame of a programmatically created UILabel when the UIContentSizeCategoryDidChangeNotification notification is fired through the following code:
private func configureNotificationCenter() {
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange) , name: NSNotification.Name("UIContentSizeCategoryDidChangeNotification"), object: nil)
}
#objc private func contentSizeCategoryDidChange() {
self.delegate?.didChangeContentSize()
}
and then in the view where the UILabel is going to be updated, I update the frame:
func didChangeContentSize() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
self.label.sizeToFit()
})
}
For some reason, the frame is not set properly without calling DispatchQueue.main.asyncAfter .... At first, I tried calling DispatchQueue.main without the 0.1 second delay since I know UI updates should always be on the main thread, but it didn't seem to make any difference.
While delaying 0.1 seconds isn't a huge deal and I don't think any users would notice, it would be great to understand what's going on and why the delay is necessary.
Edit: Here's how I'm creating the label
label = UILabel(frame: CGRect(x: 0, y: 100, width: view.frame.width, height: 200))
label.backgroundColor = .red
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = "Test title that should resize"
label.adjustsFontForContentSizeCategory = true
label.textAlignment = .center
let userFont = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1)
let pointSize = userFont.pointSize
let customFont = UIFont(name: "AvenirNext-DemiBold", size: pointSize)
label.font = UIFontMetrics.default.scaledFont(for: customFont!)
label.sizeToFit()
view.addSubview(label)
This sounds very much like an Auto Layout issue. When you don't delay, Auto Layout is adjusting the intrinsic size of the label and running after you modify the frame for the label, so your sizeToFit comes too early and uses the previous intrinsic size.
When you delay by 0.1 seconds, Auto Layout runs first and sets the intrinsic size of the label, and then your sizeToFit() call uses that new intrinsic size to set the frame.
Use Auto Layout
Make things easier on yourself by using Auto Layout. Instead of messing with frame sizes, sizeToFit, and notifications, just set constraints for the leading, trailing, and top edges of your label and Auto Layout will automatically resize your label when the font size changes:
label = UILabel()
label.backgroundColor = .red
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = "Test title that should resize"
label.adjustsFontForContentSizeCategory = true
label.textAlignment = .center
let userFont = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1)
let pointSize = userFont.pointSize
let customFont = UIFont(name: "AvenirNext-DemiBold", size: pointSize)
label.font = UIFontMetrics.default.scaledFont(for: customFont!)
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true

Swift 3 UILabel, how to word wrap after changing text

I have a UILabel which has been created programmatically and want to set the text property dynamically after its creation. I can word wrap it during init but not subsequently. After the title label text is changed it keeps one line, regardless of whether I set the number of lines to 2 or 0 and word wrap or not. After I change the title I want it to occupy 2 lines and the text is easily long enough for this to happen but it does not.
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.layer.masksToBounds = true
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.textColor = .white
return label
}()
var titleLabelText: String? {
didSet {
titleLabel.text = titleLabelText
titleLabel.layoutIfNeeded()
}
}
then later...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
titleLabelText = "some string which needs word wrapping"
}
titleLabel.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
titleLabel.widthAnchor.constraint(equalToConstant: screenWidth - 90).isActive = true
Font size is UIFont(name: "Muli", size: 12)
Here is how I load my label in viewDidLoad() but the label.text is applied in viewDidAppear()
let headerStackView: UIStackView = {
let headerArray = [welcomeLabel]
let stackView = UIStackView(arrangedSubviews: headerArray)
When you set your label to display multiple line..Remember your label must have enough height that can hold your text to be display multiple line.
Try to increase height of your label, Check width is proper and then check with your code.
If your text gets increases decrease the font size, it will automatically show complete text in your label.
and set the label line space to 0 and allow word wrap property true
select your label and try this one
AFAIK You need to add it as attributedText, then create an NSAttributedString, with your string and attributes.
let paragraphStyleWithWordWrapping = NSMutableParagraphStyle()
paragraphStyleWithWordWrapping.lineBreakMode = .byWordWrapping
let attributedString = NSAttributedString(myString, [NSParagraphStyleAttributeName: paragraphStyleWithWordWrapping])
label.attributedText = attributedString

Resources