简体   繁体   中英

Determining the maximum font size for a multi-line UILabel with maximum width

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM