I have a UILabel with three lines set to word wrap. I also set the label to have a width of 180. While this works in almost all circumstances, some languages with very long words are starting to character wrap because their length is greater than 180.
Ideally, I would have the label keep the width of 180 in all cases, except if a word is too long to fit. Then, I would like to expand the width to be the minimum size to keep the longest word in tact. How can I do that?
let myString = "thisisaveryveryveryveryverylongstring"
let myLabel = UILabel()
myLabel.text = myString
mylabel.numberOfLines = 3
myLabel.lineBreakMode = .byWordWrapping
myLabel.widthAnchor.constraint(equalToConstant: 180).isActive = true
I've tried setting a preferredWidth as well as setting the width constraint to have a priority of less than 1000, but in both circumstances the longer word still character wraps.
It's not entirely clear what you are trying to do, but I'm going to guess it's like this...
Consider the different word lengths here:
English: Some Occupation
German: Irgendeine Beschäftigung
English: Occupational Therapist
German: Beschäftigungstherapeutin
Using the first example, if the string is: "Some Occupation Irgendeine Beschäftigung" and the label width is constrained to 180-points, it will look like this:
Which, I'm guessing, is the first part of your goal -- 180-point label frame width.
However, using the second example, if the string is: "Occupational Therapist Beschäftigungstherapeutin" and the label width is constrained to 180-points, it will look like this:
The single word "Beschäftigungstherapeutin" by itself is too wide, and we get character wrapping.
So, we need to get to here:
The label width is now 206-points.
To accomplish that, we can split the string into individual words and find the widest single word:
func calcMaxWordWidth(_ str: String, forLabel: UILabel) -> CGFloat {
var maxWidth: CGFloat = 0
let words = str.components(separatedBy: " ")
words.forEach { s in
forLabel.text = s
forLabel.sizeToFit()
maxWidth = max(maxWidth, forLabel.frame.width)
}
return maxWidth
}
Here's an example you can try:
class ExampleVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var myString: String = ""
var myLabelA: UILabel = UILabel()
var myLabelB: UILabel = UILabel()
var sizingLabel: UILabel = UILabel()
// all labels have the same font
[myLabelA, myLabelB, sizingLabel].forEach { v in
v.font = .systemFont(ofSize: 17.0)
}
myLabelA.numberOfLines = 3
myLabelA.lineBreakMode = .byWordWrapping
myLabelB.numberOfLines = 3
myLabelB.lineBreakMode = .byWordWrapping
// so we can see the label frames
myLabelA.backgroundColor = .yellow
myLabelB.backgroundColor = .green
myString = "Occupational Therapist Beschäftigungstherapeutin"
//myString = "Some Occupation Irgendeine Beschäftigung"
myLabelA.text = myString
myLabelB.text = myString
myLabelA.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myLabelA)
myLabelB.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myLabelB)
// for sizingLabel
// do NOT set .translatesAutoresizingMaskIntoConstraints = false
// do NOT add it as a subview
// set number of lines to 1
sizingLabel.translatesAutoresizingMaskIntoConstraints = true
sizingLabel.numberOfLines = 1
let maxWordWidth: CGFloat = calcMaxWordWidth(myString, forLabel: sizingLabel)
let actualWidth: CGFloat = max(180.0, maxWordWidth)
print("mx:", maxWordWidth, "act:", actualWidth)
// set myLabelA width to 180.0
myLabelA.widthAnchor.constraint(equalToConstant: 180.0).isActive = true
// set myLabelB width to MAX of 180.0 or widest single word
myLabelB.widthAnchor.constraint(equalToConstant: actualWidth).isActive = true
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
myLabelA.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
myLabelA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
myLabelB.topAnchor.constraint(equalTo: myLabelA.bottomAnchor, constant: 20.0),
myLabelB.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
])
}
func calcMaxWordWidth(_ str: String, forLabel: UILabel) -> CGFloat {
var maxWidth: CGFloat = 0
let words = str.components(separatedBy: " ")
words.forEach { s in
forLabel.text = s
forLabel.sizeToFit()
maxWidth = max(maxWidth, forLabel.frame.width)
}
return maxWidth
}
}
Please note: this is meant to be a starting point. You would probably want to implement a "max width" so your label doesn't extend wider than the screen (or into another view). Also, I did very little testing of this... so it should not be considered "Production Ready"