How to calculate the width of a text string of a specific font and font-size?
Asked Answered
T

17

119

I have a UILabel that displays some chars. Like "x", "y" or "rpm". How can I calculate the width of the text in the label (it does not ues the whole available space)? This is for automatic layouting, where another view will have a bigger frame rectangle if that UILabel has a smaller text inside. Are there methods to calculate that width of the text when a UIFont and font size is specified? There's also no line-break and just one single line.

Terti answered 24/8, 2009 at 19:52 Comment(1)
I don't know how you would do this with any font type, however, if you are using a fixed width font, you can calculate using the number of characters. I'm not entirely sure of the formula.Inae
P
76

You can do exactly that via the various sizeWithFont: methods in NSString UIKit Additions. In your case the simplest variant should suffice (since you don't have multi-line labels):

NSString *someString = @"Hello World";
UIFont *yourFont = // [UIFont ...]
CGSize stringBoundingBox = [someString sizeWithFont:yourFont];

There are several variations of this method, eg. some consider line break modes or maximum sizes.

Playmate answered 25/8, 2009 at 10:50 Comment(3)
Shouldn't it be UIFont *yourFont = // [UIFont...]; though?Gunnysack
"sizeWithFont:" is deprecated. Answer by wcochran should be the marked one.Shelter
Since you have the green checkmark, you should change your answer to use sizeWithAttributes.Iconolatry
I
112

Since sizeWithFont is deprecated, I'm just going to update my original answer to using Swift 4 and .size

//: Playground - noun: a place where people can play
    
import UIKit
           
if let font = UIFont(name: "Helvetica", size: 24) {
   let fontAttributes = [NSAttributedString.Key.font: font]
   let text = "Your Text Here"
   let size = (text as NSString).size(withAttributes: fontAttributes)
}

The size should be the onscreen size of "Your Text Here" in points.

Iconolatry answered 24/8, 2009 at 20:0 Comment(3)
Thank you for posting this solution. I wrote an extension based on your answer. It's posted below.Zebapda
What will be the corresponding function in swift 4?Highkey
I'm using SwiftUI, but there are no suitable counterparts. I'm handling this if #if checks to import UIKit vs. AppKit. One problem I found, though, is that in the documentation, it says I need to pass a UIFont as an NSAttributedString.Key.font even in macOS (where UIFont, part of UIKit, doesn't exist). Link to documentation: developer.apple.com/documentation/foundation/nsattributedstring/…Gage
R
81

sizeWithFont: is now deprecated, use sizeWithAttributes: instead:

UIFont *font = [UIFont fontWithName:@"Helvetica" size:30];
NSDictionary *userAttributes = @{NSFontAttributeName: font,
                                 NSForegroundColorAttributeName: [UIColor blackColor]};
NSString *text = @"hello";
...
const CGSize textSize = [text sizeWithAttributes: userAttributes];
Radicalism answered 27/2, 2014 at 20:20 Comment(1)
Note: sizeWithAttributes: method returns fractional sizes; to use a returned size to size views, you must raise its value to the nearest higher integer using the ceil function.Taunt
P
76

You can do exactly that via the various sizeWithFont: methods in NSString UIKit Additions. In your case the simplest variant should suffice (since you don't have multi-line labels):

NSString *someString = @"Hello World";
UIFont *yourFont = // [UIFont ...]
CGSize stringBoundingBox = [someString sizeWithFont:yourFont];

There are several variations of this method, eg. some consider line break modes or maximum sizes.

Playmate answered 25/8, 2009 at 10:50 Comment(3)
Shouldn't it be UIFont *yourFont = // [UIFont...]; though?Gunnysack
"sizeWithFont:" is deprecated. Answer by wcochran should be the marked one.Shelter
Since you have the green checkmark, you should change your answer to use sizeWithAttributes.Iconolatry
Z
74

Update Sept 2019

This answer is a much cleaner way to do it using new syntax.

Original Answer

Based on Glenn Howes' excellent answer, I created an extension to calculate the width of a string. If you're doing something like setting the width of a UISegmentedControl, this can set the width based on the segment's title string.

extension String {

    func widthOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.width
    }

    func heightOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.height
    }

    func sizeOfString(usingFont font: UIFont) -> CGSize {
        let fontAttributes = [NSAttributedString.Key.font: font]
        return self.size(withAttributes: fontAttributes)
    }
}

usage:

    // Set width of segmentedControl
    let starString = "⭐️"
    let starWidth = starString.widthOfString(usingFont: UIFont.systemFont(ofSize: 14)) + 16
    segmentedController.setWidth(starWidth, forSegmentAt: 3)
Zebapda answered 6/10, 2016 at 19:18 Comment(5)
What will be solution for swift 4?Highkey
Why not just one function that returns size (CGSize)? Why do the work twice?Radicalism
@Radicalism Updated. Great call.Zebapda
I don't know how, but this gives me incorrect sizes.Roughish
Nevermind, this works but from my tests you need to add additional padding of at least 15 in order to prevent the text from being truncated.Roughish
H
57

Oneliner in Swift 4.2 🔸

let size = "abc".size(withAttributes:[.font: UIFont.systemFont(ofSize: 18.0)])
Heigho answered 9/8, 2018 at 7:49 Comment(0)
D
52

Swift-5

Use intrinsicContentSize to find the text height and width.

yourLabel.intrinsicContentSize.width

This will work even you have custom spacing between your string like "T E X T"

Dieter answered 18/9, 2018 at 3:5 Comment(0)
W
11

iOS 16 update:

UILabel.textRect(forBounds: CGRect, limitedToNumberOfLines: Int)

If you're struggling to get text width with multiline support, so you can use the next code (Swift 5):

func width(text: String, height: CGFloat) -> CGFloat {
    let attributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.systemFont(ofSize: 17)
    ]
    let attributedText = NSAttributedString(string: text, attributes: attributes)
    let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height)
    let textWidth = attributedText.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil).width.rounded(.up)
    
    return textWidth
}

And the same way you could find text height if you need to (just switch the constraintBox implementation):

let constraintBox = CGSize(width: maxWidth, height: .greatestFiniteMagnitude)

Or here's a unified function to get text size with multiline support:

func labelSize(for text: String, maxWidth: CGFloat, maxHeight: CGFloat) -> CGSize {
    let attributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.systemFont(ofSize: 17)
    ]
    
    let attributedText = NSAttributedString(string: text, attributes: attributes)
    
    let constraintBox = CGSize(width: maxWidth, height: maxHeight)
    let rect = attributedText.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil).integral
    
    return rect.size
}

Usage:

let textSize = labelSize(for: "SomeText", maxWidth: contentView.bounds.width, maxHeight: .greatestFiniteMagnitude)
let textHeight = textSize.height.rounded(.up)
let textWidth = textSize.width.rounded(.up)
Wailoo answered 16/12, 2019 at 13:40 Comment(3)
This part of your code ".rounded(.up)" save me trouble, because in my code, the returned width was like 0.2-0.3 less that it should be, and I was not thinking of doing a round up. Thank you!Petersham
This no longer works for me (iOS 16). I was able to get it to work about the same using UILabel.textRect(forBounds: CGRect, limitedToNumberOfLines: Int).Deane
It works fine in iOS 16Wanids
A
8

This simple extension in Swift works well.

extension String {
    func size(OfFont font: UIFont) -> CGSize {
        return (self as NSString).size(attributes: [NSFontAttributeName: font])
    }
}

Usage:

let string = "hello world!"
let font = UIFont.systemFont(ofSize: 12)
let width = string.size(OfFont: font).width // size: {w: 98.912 h: 14.32}
Acceleration answered 9/7, 2017 at 5:56 Comment(0)
A
7

For Swift 5.4

extension String {
    func SizeOf_String( font: UIFont) -> CGSize {
        let fontAttribute = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttribute)  // for Single Line
       return size;
    }
}

Use it like...

        let Str = "ABCDEF"
        let Font =  UIFont.systemFontOfSize(19.0)
        let SizeOfString = Str.SizeOfString(font: Font!)
Adjust answered 6/10, 2017 at 8:27 Comment(1)
check variable names for clean code!!Tipton
P
3

Swift 4

extension String {
    func SizeOf(_ font: UIFont) -> CGSize {
        return self.size(withAttributes: [NSAttributedStringKey.font: font])
    }
}
Plow answered 27/8, 2018 at 10:24 Comment(0)
C
2

This is for swift 2.3 Version. You can get the width of string.

var sizeOfString = CGSize()
if let font = UIFont(name: "Helvetica", size: 14.0)
    {
        let finalDate = "Your Text Here"
        let fontAttributes = [NSFontAttributeName: font] // it says name, but a UIFont works
        sizeOfString = (finalDate as NSString).sizeWithAttributes(fontAttributes)
    }
Conspire answered 23/9, 2016 at 6:49 Comment(0)
V
1

Not sure how efficient this is, but I wrote this function that returns the point size that will fit a string to a given width:

func fontSizeThatFits(targetWidth: CGFloat, maxFontSize: CGFloat, font: UIFont) -> CGFloat {
    var variableFont = font.withSize(maxFontSize)
    var currentWidth = self.size(withAttributes: [NSAttributedString.Key.font:variableFont]).width

    while currentWidth > targetWidth {
        variableFont = variableFont.withSize(variableFont.pointSize - 1)
        currentWidth = self.size(withAttributes: [NSAttributedString.Key.font:variableFont]).width
    }

    return variableFont.pointSize
}

And it would be used like this:

textView.font = textView.font!.withSize(textView.text!.fontSizeThatFits(targetWidth: view.frame.width, maxFontSize: 50, font: textView.font!))

Venlo answered 19/3, 2020 at 0:25 Comment(0)
B
1

Very quick solution when you already have a label and you need a fixed width for a constraint, such as for a fixed column width:

Typical issue, you have somelabel: UILabel set up, but you need a max width, as if for an "Example String".

ie, you want the label to compress if it's wider than "Example String" would be when rendered.

It's just

let productColumn = ("Example String" as NSString)
        .size(withAttributes:
        [NSAttributedString.Key.font: somelabel.font!]).width

and you're done.

You'd then have something like

...
someLabel.widthAnchor.constraint(equalToConstant: productColumn),
Bunnybunow answered 27/3 at 17:37 Comment(0)
H
0

The way I am doing it my code is to make an extension of UIFont: (This is Swift 4.1)

extension UIFont {


    public func textWidth(s: String) -> CGFloat
    {
        return s.size(withAttributes: [NSAttributedString.Key.font: self]).width
    }

} // extension UIFont
Happen answered 31/10, 2018 at 6:19 Comment(0)
T
0

SwiftUI with Swift5

In SwiftUI, you could not find an easy way to convert UIFont to Font. So the previous answers may not work. You could use GeometryReader{ geometryProxy in } inside the overlay() modifier to get the Text size. Be careful that if you don't use it inside overlay(), the View will expand to as much as it can.

If you want to pass the variable out, you may need to write a View extension function to do so.

Text("Lat.: \(latitude), Lon.: \(longitude) ")
    .font(Font.custom("ChalkboardSE-Light",size: 20))
    .overlay(
    GeometryReader{ geometryProxy in
        Image("button_img")
             .resizable()
             .frame(width: 10 , height: 10)
             .offset(x: geometryProxy.size.width)
             .extensionFunction{ //... }
              })
    

I copied the extension function from another thread a couple days ago.

extension View{
    func extensionFunction(_ closure:() -> Void) -> Self{
        closure()
        return self
    }
    
}
Twi answered 13/9, 2021 at 10:36 Comment(0)
B
0

For 2022, updated.

For this very old question/answers, I'm just putting in the current way to do it that always works, which is a combo of answers below.

By always I mean the most common case - which is unfortunately the trickest case - when you're literally modifying something inside of drawText#in rect.

fileprivate extension String {
    func realSize(font: UIFont) -> CGSize {
        var s = self.size(withAttributes: [NSAttributedString.Key.font: font])
        s.width = size.width.rounded(.up)
        s.height = size.height.rounded(.up)
        return s
    }
}

Note that it rounds the size (specifically, up). Why? In most cases you'll want this since typically layouts have even sized points and that's "what you'll need" to match other rendering.

(Note that the difference is absolutely tiny anyway). Try it both ways to see, as you wish.

Hence for example something like

class StableLabel: UILabel {

    override var intrinsicContentSize: CGSize {
        // we arrange to return some fixed-always size ...
    }

    override func drawText(in rect: CGRect) {
        let painful = text!.realSize(font: font)

        // Assuming the size of this label is absolutely fixed by the layout,
        // we want to ALWAYS draw the text sitting at the BOTTOM CENTER
        // of the fixed size of this label, even as the size is changed.
        // For example a focus or such may be changing the point size.

        let left = knownSize.width - painful.width) / 2.0
        let down = knownSize.height - painful.height)
        let r = CGRect(origin: CGPoint(x: left, y: down), size: painful)
        super.drawText(in: r)
    }
Bunnybunow answered 3/10, 2022 at 21:16 Comment(0)
P
0

For SwiftUI you can't use Font since it has no size(widthAttributes: But if you define the same UIFont then you can get the size of this Text Label. Then these methods work as a String Extension:

func widthOfString(usingUIFont font: UIFont) -> CGFloat {
    let fontAttributes = [NSAttributedString.Key.font: font]
    //Only works with UIFont not Font
    let size = self.size(withAttributes: fontAttributes)
    return size.width
}

func heightOfString(usingUIFont font: UIFont) -> CGFloat {
    let fontAttributes = [NSAttributedString.Key.font: font]
    //Only works with UIFont not Font
    let size = self.size(withAttributes: fontAttributes)
    return size.height
}

func heightOfStringBasedOnWidth(usingUIFont font: UIFont, width:CGFloat) -> CGFloat {
    let fontAttributes = [NSAttributedString.Key.font: font]
    //Only works with UIFont not Font, very nice method since you can limit the width
    //The below method just returns the height of the font regardless of width so you need to iterate over it
    let size = self.size(withAttributes: fontAttributes)
    if(size.width < width)
    {
        return size.height
    }
    else
    {
        var desiredWidth = size.width
        var lines:Int = 1
        while desiredWidth > width {
            desiredWidth -= width
            lines += 1
        }
        return size.height * CGFloat(lines)
    }
}
Pythagoreanism answered 2/3, 2023 at 21:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.