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.
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.
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.
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];
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 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.
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)
Oneliner in Swift 4.2 🔸
let size = "abc".size(withAttributes:[.font: UIFont.systemFont(ofSize: 18.0)])
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"
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)
UILabel.textRect(forBounds: CGRect, limitedToNumberOfLines: Int)
. –
Deane 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!)
Swift 4
extension String {
func SizeOf(_ font: UIFont) -> CGSize {
return self.size(withAttributes: [NSAttributedStringKey.font: font])
}
}
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)
}
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!))
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),
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
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)
}
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)
}
}
© 2022 - 2024 — McMap. All rights reserved.