¿Cómo calcular el ancho de una cadena de texto de una fuente y tamaño de fuente específicos?

82

Tengo un UILabel que muestra algunos caracteres. Como "x", "y" o "rpm". ¿Cómo puedo calcular el ancho del texto en la etiqueta (no ocupa todo el espacio disponible)? Esto es para el diseño automático, donde otra vista tendrá un rectángulo de marco más grande si esa UILabel tiene un texto más pequeño adentro. ¿Existen métodos para calcular ese ancho del texto cuando se especifica un UIFont y un tamaño de fuente? Tampoco hay salto de línea y solo una línea.


fuente
No sé cómo haría esto con cualquier tipo de fuente, sin embargo, si está usando una fuente de ancho fijo, puede calcular usando el número de caracteres. No estoy completamente seguro de la fórmula.
jgallant

Respuestas:

73

Puede hacer exactamente eso a través de los diversos sizeWithFont:métodos en NSString UIKit Additions . En su caso, la variante más simple debería ser suficiente (ya que no tiene etiquetas de varias líneas):

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

Hay varias variaciones de este método, por ejemplo. algunos consideran modos de salto de línea o tamaños máximos.

Daniel Rinser
fuente
1
¿No debería ser UIFont * yourFont = // [UIFont ...]; ¿aunque?
PinkFloydRocks
Vaya, sí, claro ... Fijo.
Daniel Rinser
11
"sizeWithFont:" está obsoleto. La respuesta de wcochran debe ser la marcada.
Ferran Maylinch
4
Como tiene la marca de verificación verde, debe cambiar su respuesta para usar sizeWithAttributes.
Glenn Howes
80

Dado que sizeWithFontestá en desuso, solo actualizaré mi respuesta original para usar Swift 4 y.size

//: Playground - noun: a place where people can play

import UIKit

if let font = UIFont(name: "Helvetica", size: 24) {
   let fontAttributes = [NSAttributedStringKey.font: font]
   let myText = "Your Text Here"
   let size = (myText as NSString).size(withAttributes: fontAttributes)
}

El tamaño debe ser el tamaño en pantalla de "Su texto aquí" en puntos.

Glenn Howes
fuente
Gracias por publicar esta solución. Escribí una extensión basada en tu respuesta. Está publicado a continuación.
Adrian
¿Cuál será la función correspondiente en swift 4?
Swayambhu
77

sizeWithFont:ahora está en desuso, use sizeWithAttributes:en su lugar:

UIFont *font = [UIFont fontWithName:@"Helvetica" size:30];
NSDictionary *userAttributes = @{NSFontAttributeName: font,
                                 NSForegroundColorAttributeName: [UIColor blackColor]};
NSString *text = @"hello";
...
const CGSize textSize = [text sizeWithAttributes: userAttributes];
wcochran
fuente
8
Nota: el sizeWithAttributes:método devuelve tamaños fraccionarios; para usar un tamaño devuelto para cambiar el tamaño de las vistas, debe aumentar su valor al número entero superior más cercano mediante la función ceil.
Allen
55

Actualización de septiembre de 2019

Esta respuesta es una forma mucho más limpia de hacerlo usando una nueva sintaxis.

Respuesta original

Basado en la excelente respuesta de Glenn Howes , creé una extensión para calcular el ancho de una cadena. Si está haciendo algo como establecer el ancho de a UISegmentedControl, esto puede establecer el ancho según la cadena del título del segmento.

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

uso:

    // Set width of segmentedControl
    let starString = "⭐️"
    let starWidth = starString.widthOfString(usingFont: UIFont.systemFont(ofSize: 14)) + 16
    segmentedController.setWidth(starWidth, forSegmentAt: 3)
Adrian
fuente
1
¿Cuál será la solución para Swift 4?
Swayambhu
1
¿Por qué no solo una función que devuelve tamaño (CGSize)? ¿Por qué hacer el trabajo dos veces?
wcochran
@wcochran Actualizado. Gran llamada.
Adrian
1
No sé cómo, pero esto me da tamaños incorrectos.
Max
No importa, esto funciona, pero según mis pruebas, debes agregar un relleno adicional de al menos 15 para evitar que el texto se trunque.
Max
33

Swift-5

Utilice intrinsicContentSize para encontrar la altura y el ancho del texto.

yourLabel.intrinsicContentSize.width

Esto funcionará incluso si tiene un espacio personalizado entre su cadena como "TEX T"

Alok
fuente
26

Oneliner en Swift 4.2 🔸

let size = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:18.0)])
eonista
fuente
6

Esta simple extensión en Swift funciona bien.

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

Uso:

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
fuente
3

Rápido 4

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

Esto es para la versión rápida 2.3. Puede obtener el ancho de la cuerda.

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)
    }
Mandeep Singh
fuente
2

Si tiene dificultades para obtener el ancho del texto con soporte multilínea , entonces puede usar el siguiente código ( 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
}

Y de la misma manera que puede encontrar la altura del texto si lo necesita (simplemente cambie la implementación de la caja de restricción):

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

O aquí hay una función unificada para obtener el tamaño del texto con soporte multilínea:

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
}

Uso:

let textSize = labelSize(for: "SomeText", maxWidth: contentView.bounds.width, maxHeight: .greatestFiniteMagnitude)
let textHeight = textSize.height.rounded(.up)
let textWidth = textSize.width.rounded(.up)
atereshkov
fuente
1

Para Swift 3.0+

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

Úselo como ...

        let Str = "ABCDEF"
        let Font =  UIFont.systemFontOfSize(19.0)
        let SizeOfString = Str.SizeOfString(font: Font!)
Lakhdeep Singh
fuente
1

No estoy seguro de cuán eficiente es esto, pero escribí esta función que devuelve el tamaño de punto que se ajustará a una cadena a un ancho determinado:

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
}

Y se usaría así:

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

David Chopin
fuente
0

La forma en que lo estoy haciendo mi código es hacer una extensión de UIFont: (Esto es Swift 4.1)

extension UIFont {


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

} // extension UIFont
MarkAurelius
fuente