101

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.

  • 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. – jgallant Aug 24 '09 at 19:57

14 Answers14

96

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: 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.

aheze
  • 16,189
  • 4
  • 33
  • 81
Glenn Howes
  • 5,237
  • 1
  • 23
  • 29
80

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];
Doug
  • 776
  • 7
  • 12
wcochran
  • 9,195
  • 4
  • 55
  • 64
  • 9
    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. – Allen Apr 14 '15 at 04:13
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.

Pang
  • 9,073
  • 146
  • 84
  • 117
Daniel Rinser
  • 8,785
  • 4
  • 40
  • 39
71

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)
Adrian
  • 15,330
  • 17
  • 106
  • 173
48

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"

Alok
  • 23,024
  • 5
  • 38
  • 65
45

Oneliner in Swift 4.2

let size = "abc".size(withAttributes:[.font: UIFont.systemFont(ofSize: 18.0)])
Sentry.co
  • 4,826
  • 41
  • 35
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}
JsW
  • 1,534
  • 3
  • 18
  • 34
6

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)
atereshkov
  • 3,705
  • 1
  • 32
  • 45
4

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!)
Lakhdeep Singh
  • 995
  • 9
  • 9
3

Swift 4

extension String {
    func SizeOf(_ font: UIFont) -> CGSize {
        return self.size(withAttributes: [NSAttributedStringKey.font: font])
    }
}
Almudhafar
  • 867
  • 11
  • 25
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)
    }
Balagurunathan Marimuthu
  • 2,879
  • 4
  • 27
  • 42
Mandeep Singh
  • 2,740
  • 1
  • 18
  • 30
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!))

David Chopin
  • 2,324
  • 2
  • 15
  • 35
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
MarkAurelius
  • 1,003
  • 11
  • 24
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
    }
    
}
William Tong
  • 421
  • 7
  • 14