如何计算特定字体和字体大小的文本字符串的宽度?

我有一个 UILabel,它显示一些字符。比如“ x”“ y”或者“ rpm”。如何计算标签中文本的宽度(它不占用整个可用空间) ?这是为自动布局,其中另一个视图将有一个更大的框架矩形,如果 UILabel 有一个较小的文本内。当指定 UIFont 和字体大小时,是否有方法计算文本的宽度?也没有分行符,只有一行。

129754 次浏览

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.

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.

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];

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)
}

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)

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}

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!)

Oneliner in Swift 4.2 🔸

let size = "abc".size(withAttributes:[.font: UIFont.systemFont(ofSize: 18.0)])

Swift 4

extension String {
func SizeOf(_ font: UIFont) -> CGSize {
return self.size(withAttributes: [NSAttributedStringKey.font: font])
}
}

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"

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

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)

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!))

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
}
    

}

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)
}