Calcule el tamaño de UILabel basado en String in Swift

183

Estoy tratando de calcular la altura de un UILabel en función de diferentes longitudes de cadena.

func calculateContentHeight() -> CGFloat{
    var maxLabelSize: CGSize = CGSizeMake(frame.size.width - 48, CGFloat(9999))
    var contentNSString = contentText as NSString
    var expectedLabelSize = contentNSString.boundingRectWithSize(maxLabelSize, options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont.systemFontOfSize(16.0)], context: nil)
    print("\(expectedLabelSize)")
    return expectedLabelSize.size.height

}

Arriba está la función actual que uso para determinar la altura pero no funciona. Agradecería mucho cualquier ayuda que pueda obtener. Perfeccionaría la respuesta en Swift y no en el Objetivo C.

Cody Weaver
fuente
duplicado prueba este stackoverflow.com/a/61887135/6314955
Malith Kuruwita

Respuestas:

518

Use una extensión en String

Swift 3

extension String {
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

        return ceil(boundingBox.height)
    }

    func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

        return ceil(boundingBox.width)
    }
}

y también en NSAttributedString(que a veces es muy útil)

extension NSAttributedString {
    func height(withConstrainedWidth width: CGFloat) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

        return ceil(boundingBox.height)
    }

    func width(withConstrainedHeight height: CGFloat) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

        return ceil(boundingBox.width)
    }
}

Swift 4

Simplemente cambie el valor de attributesen los extension Stringmétodos

de

[NSFontAttributeName: font]

a

[.font : font]
Kaan Dedeoglu
fuente
2
@CodyWeaver verifique la edición para el método widthWithConstrainedHeight.
Kaan Dedeoglu
77
@KaanDedeoglu, ¿cómo funcionaría esto con cadenas de altura dinámicas como cuando usa "numberOfLines" = 0 (que podría ser específico de UILabel, no estoy seguro) o lineBreakMode ByWordWrapping. Mi conjetura era agregar eso a los atributos como este, [NSFontAttributeName: font, NSLineBreakMode: .ByWordWrapping]pero no funcionó
Francisc0
1
Creo que descubrí mi respuesta. Necesito usar NSParagraphStyleAttributeName : styledonde el estilo es NSMutableParagraphStyle
Francisc0
44
Necesito escribir 'self.boundingRect' en lugar de 'boundingRect'; de lo contrario, aparece un error de compilación.
Mike M
1
Una cosa con esta respuesta que he encontrado al usarla sizeForItemAtIndexPathen una UICollectionViewes que parece anular el retorno parainsetForSectionAt
Zack Shapiro
15

Para texto multilínea, esta respuesta no funciona correctamente. Puede crear una extensión de cadena diferente utilizando UILabel

extension String {
func height(constraintedWidth width: CGFloat, font: UIFont) -> CGFloat {
    let label =  UILabel(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))
    label.numberOfLines = 0
    label.text = self
    label.font = font
    label.sizeToFit()

    return label.frame.height
 }
}

El UILabel obtiene un ancho fijo y .numberOfLines se establece en 0. Al agregar el texto y llamar a .sizeToFit (), se ajusta automáticamente a la altura correcta.

El código está escrito en Swift 3 🔶🐦

Sn0wfreeze
fuente
15
sizeToFit, sin embargo, presenta un millón de problemas de rendimiento debido a los muchos pases del dibujo. Calcular el tamaño manualmente es mucho más barato en recursos
Marco Pappalardo
2
Esta solución debe establecer el UIFont de UILabel para garantizar la altura correcta.
Matthew Spencer
funciona perfectamente para mí, incluso representa cadenas vacías (a diferencia de la respuesta aceptada). ¡Muy útil para calcular la altura de una tableView con celdas de altura automáticas!
Hendies
Descubrí que la respuesta aceptada funcionaba para un ancho fijo, pero no para una altura fija. Para una altura fija, solo aumentaría el ancho para adaptarse a todo en una línea, a menos que haya un salto de línea en el texto. Aquí está mi respuesta alternativa: Mi respuesta
MSimic
2
He publicado una solution-- similar sin la necesidad de una llamada asizeToFit
ryang
6

Heres una solución simple que funciona para mí ... similar a algunos de los otros publicados, pero no incluye la necesidad de llamar sizeToFit

Tenga en cuenta que esto está escrito en Swift 5

let lbl = UILabel()
lbl.numberOfLines = 0
lbl.font = UIFont.systemFont(ofSize: 12) // make sure you set this correctly 
lbl.text = "My text that may or may not wrap lines..."

let width = 100.0 // the width of the view you are constraint to, keep in mind any applied margins here

let height = lbl.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel).height

Esto maneja el ajuste de línea y tal. No es el código más elegante, pero hace el trabajo.

RyanG
fuente
2
extension String{

    func widthWithConstrainedHeight(_ height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: height)

        let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

        return ceil(boundingBox.width)
    }

    func heightWithConstrainedWidth(_ width: CGFloat, font: UIFont) -> CGFloat? {
        let constraintRect = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

        return ceil(boundingBox.height)
    }

}
CrazyPro007
fuente
1

Esta es mi respuesta en Swift 4.1 y Xcode 9.4.1

//This is your label
let proNameLbl = UILabel(frame: CGRect(x: 0, y: 20, width: 300, height: height))
proNameLbl.text = "This is your text"
proNameLbl.font = UIFont.systemFont(ofSize: 17)
proNameLbl.numberOfLines = 0
proNameLbl.lineBreakMode = .byWordWrapping
infoView.addSubview(proNameLbl)

//Function to calculate height for label based on text
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat {
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
    label.numberOfLines = 0
    label.lineBreakMode = NSLineBreakMode.byWordWrapping
    label.font = font
    label.text = text

    label.sizeToFit()
    return label.frame.height
}

Ahora llamas a esta función

//Call this function
let height = heightForView(text: "This is your text", font: UIFont.systemFont(ofSize: 17), width: 300)
print(height)//Output : 41.0
iOS
fuente
1

Descubrí que la respuesta aceptada funcionaba para un ancho fijo, pero no para una altura fija. Para una altura fija, solo aumentaría el ancho para adaptarse a todo en una línea, a menos que haya un salto de línea en el texto.

La función de ancho llama a la función de altura varias veces, pero es un cálculo rápido y no noté problemas de rendimiento al usar la función en las filas de una UITable.

extension String {

    public func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font : font], context: nil)

        return ceil(boundingBox.height)
    }

    public func width(withConstrainedHeight height: CGFloat, font: UIFont, minimumTextWrapWidth:CGFloat) -> CGFloat {

        var textWidth:CGFloat = minimumTextWrapWidth
        let incrementWidth:CGFloat = minimumTextWrapWidth * 0.1
        var textHeight:CGFloat = self.height(withConstrainedWidth: textWidth, font: font)

        //Increase width by 10% of minimumTextWrapWidth until minimum width found that makes the text fit within the specified height
        while textHeight > height {
            textWidth += incrementWidth
            textHeight = self.height(withConstrainedWidth: textWidth, font: font)
        }
        return ceil(textWidth)
    }
}
MSimic
fuente
1
lo que es minimumTextWrapWidth:CGFloat?
Vyachaslav Gerchicov
Es solo un valor semilla para los cálculos en la función. Si espera que el ancho sea grande, elegir un mínimo mínimo TextWrapWidth hará que el ciclo while pase por iteraciones adicionales. Entonces, cuanto mayor sea el ancho mínimo, mejor, pero si es mayor que el ancho real requerido, siempre será el ancho devuelto.
MSimic
0

Verifique la altura del texto de la etiqueta y está trabajando en ello

let labelTextSize = ((labelDescription.text)! as NSString).boundingRect(
                with: CGSize(width: labelDescription.frame.width, height: .greatestFiniteMagnitude),
                options: .usesLineFragmentOrigin,
                attributes: [.font: labelDescription.font],
                context: nil).size
            if labelTextSize.height > labelDescription.bounds.height {
                viewMoreOrLess.hide(byHeight: false)
                viewLess.hide(byHeight: false)
            }
            else {
                viewMoreOrLess.hide(byHeight: true)
                viewLess.hide(byHeight: true)

            }
CSE 1994
fuente
0

Esta solución ayudará a calcular la altura y el ancho en tiempo de ejecución.

    let messageText = "Your Text String"
    let size = CGSize.init(width: 250, height: 1000)
    let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
    let estimateFrame = NSString(string: messageText).boundingRect(with:  size, options: options, attributes: [NSAttributedString.Key.font: UIFont(name: "HelveticaNeue", size: 17)!], context: nil)

Aquí puede calcular la altura estimada que tomaría su cadena y pasarla al marco UILabel.

estimateFrame.Width
estimateFrame.Height 
Shanu Singh
fuente
0

Swift 5:

Si tiene UILabel y de alguna manera, limitingRect no funciona para usted (me enfrenté a este problema. Siempre devolvió 1 altura de línea). Hay una extensión para calcular fácilmente el tamaño de la etiqueta.

extension UILabel {
    func getSize(constrainedWidth: CGFloat) -> CGSize {
        return systemLayoutSizeFitting(CGSize(width: constrainedWidth, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    }
}

Puedes usarlo así:

let label = UILabel()
label.text = "My text\nIs\nAwesome"
let labelSize = label.getSize(constrainedWidth:200.0)

Funciona para mi

George Sabanov
fuente
-2
@IBOutlet weak var constraintTxtV: NSLayoutConstraint!
func TextViewDynamicallyIncreaseSize() {
    let contentSize = self.txtVDetails.sizeThatFits(self.txtVDetails.bounds.size)
    let higntcons = contentSize.height
    constraintTxtV.constant = higntcons
}
Niraj Paul
fuente
55
Su respuesta no solo debe consistir en código, sino también en una explicación sobre el código. Consulte Cómo responder para obtener más detalles.
MechMK1
Si bien este código puede responder a la pregunta, proporcionar un contexto adicional con respecto a por qué y / o cómo este código responde a la pregunta mejora su valor a largo plazo.
Isma
Esta respuesta es incompleta. Se refiere a variables importantes cuyos tipos son desconocidos, lo que
anula