Now I know there are dozens of questions that ask this same thing however most of them dont pertain to multi-line labels or have answers that dont work or are outdated. I have looked.
I am trying to figure out the minimum font size needed to display message
inside a 3 line UILabel taking up only maximumContentWidth
width and with font UIFont.systemFont(.regular)
. Now I realize that there are properties within UILabel that allow it to automatically shrink to fit however that wont work for me. I need to determine the specific number for the minimum font size in order to apply it to multiple other UILabels some of them displaying significantly less text.
I do have a maximum font size of 20 and a minimum font size of 13.
I am currently partially using a solution found here however it only works in theory. I am not exactly certain why but when I did some test text that was great on strings like "fa fa fa ..." where fa is repeated until I know visually that one fa cannot fit in maximum font. However for strings like "Ww wwwwwwwww wwww wwww ww wwww www w www www wwwwwww www wwwwwwww wwwwwww. www www wwwwwwwwww wwwwwww wwww www wwwwww www wwwwwwwwww ww wwwwww wwww www 10,000 wwww. wwww ww ww ww, ww www www wwwwwwwww www www wwwww www wwww wwwwwww www wwwwwwwwwwww." the system completely breaks down not making the text as small as it needs to be.
So here is the detector
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
And here is how I am using it:
var testLabel = UILabel()
var font = 20
testLabel.numberOfLines = 3
testLabel.frame.size.width = maximumContentWidth
while font > 13 {
testLabel.text = message
testLabel.font = UIFont.systemFont(ofSize: font, weight: .regular)
testLabel.sizeToFit()
if !testLabel.isTruncated {
break
}
font -= 0.1
}
Why is this method not working and what is a suitable substitute for it?
If I got your code right, you're trying to decrease font size until it will fit the UILabel
size. So you the size of the font would be as large as possible.
The main problem is in your while
loop. On each step, you set up new font
and call sizeToFit
what makes you label frame change to fit the font size. After that your isTruncated
will return false
.
You need to set up your UILabel
s frame on each step manually, instead of calling sizeToFit()
.
testLabel.numberOfLines = 3
testLabel.text = message
while font > 13 {
testLabel.font = UIFont.systemFont(ofSize: font, weight: .regular)
testLabel.frame.size = CGSize(width: maximumContentWidth,
height: maximumContentHeight)
if !testLabel.isTruncated {
break
}
font -= 0.1
}
After that, you will set the desired labels size and check if the new font size let the text to fit it.
UPDATE
I didn't mention that you want to make sure you UILabel
doesn't have more than 3 line. This is a bit tricky because there is no easy or simple way to know how many lines does UILabel
have. But that's the solution that I've used in my projects
extension UILabel {
func getLinesCount() -> Int {
let text = self.text ?? ""
let rectWidth = self.frame.width
let myFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
let attStr = NSMutableAttributedString(string: text)
let range = NSRange(location: 0, length: attStr.length)
attStr.addAttribute(kCTFontAttributeName as NSAttributedStringKey, value: myFont, range: range)
let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path: CGMutablePath = CGMutablePath()
let rect = CGRect(x: 0, y: 0, width: rectWidth, height: CGFloat.greatestFiniteMagnitude)
path.addRect(rect, transform: .identity)
let frame: CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else { return 0 }
return lines.count
}
}
This method returns you a number of lines for your label, based on its font
and width
. You can use it in your while
loop to check how does it change.
Hope it helps you.