Descobrir o tamanho do UILabel com base na String no Swift


183

Eu estou tentando calcular a altura de um UILabel com base em diferentes comprimentos de seqüência de caracteres.

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

}

Acima está a função atual que uso para determinar a altura, mas ela não está funcionando. Eu apreciaria muito qualquer ajuda que eu possa obter. Eu preferiria a resposta em Swift e não no objetivo C.


Respostas:


518

Use uma extensão em 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)
    }
}

e também em NSAttributedString(que é muito útil às vezes)

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

Apenas mude o valor para attributesnos extension Stringmétodos

de

[NSFontAttributeName: font]

para

[.font : font]

2
@CodyWeaver verifique a edição para o método widthWithConstrainedHeight.
Kaan Dedeoglu

7
@KaanDedeoglu, como isso funcionaria com seqüências de altura dinâmicas, como quando você usa "numberOfLines" = 0 (que pode ser específico do UILabel, não tenho certeza) ou lineBreakMode ByWordWrapping. Meu palpite era de acrescentar que aos atributos como este [NSFontAttributeName: font, NSLineBreakMode: .ByWordWrapping], mas não funcionou
Francisc0

1
Acho que descobri minha resposta. Eu preciso usar NSParagraphStyleAttributeName : styleonde o estilo é NSMutableParagraphStyle
Francisc0:

4
Preciso escrever 'self.boundingRect' em vez de 'boundingRect', caso contrário, recebo um erro de compilação.
13137 Mike M

1
Uma coisa com esta resposta que eu encontrei quando o utilizar sizeForItemAtIndexPathnum UICollectionViewé que parece substituir a troca deinsetForSectionAt
Zack Shapiro

15

Para texto com várias linhas, esta resposta não está funcionando corretamente. Você pode criar uma extensão String diferente usando 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
 }
}

O UILabel obtém uma largura fixa e o .numberOfLines é definido como 0. Ao adicionar o texto e chamar .sizeToFit (), ele se ajusta automaticamente à altura correta.

O código está escrito em Swift 3 🔶🐦


15
O sizeToFit, no entanto, apresenta um milhão de problemas de desempenho devido às muitas passagens do desenho. Calcular o tamanho manualmente é a maneira mais barata de recursos
Marco Pappalardo

2
Esta solução deve definir o UIFont do UILabel para garantir a altura correta.
Matthew Spencer

funciona perfeitamente para mim - é responsável por seqüências de caracteres vazias (ao contrário da resposta aceita). Muito útil para calcular a altura de um tableView com células de altura automáticas!
Hendies

Descobri que a resposta aceita funcionava para uma largura fixa, mas não para uma altura fixa. Para uma altura fixa, apenas aumentaria a largura para caber tudo em uma linha, a menos que houvesse uma quebra de linha no texto. Aqui está a minha resposta alternativa: Minha resposta
MSimic

2
sizeToFit
Publiquei

6

Heres uma solução simples que está funcionando para mim ... semelhante a alguns dos outros postados, mas não inclui a necessidade de chamar sizeToFit

Observe que isso está escrito no 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

Isso lida com quebra de linha e tal. Não é o código mais elegante, mas faz o trabalho.


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

}

1

Esta é a minha resposta no Swift 4.1 e no 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
}

Agora você chama essa função

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

1

Descobri que a resposta aceita funcionava para uma largura fixa, mas não para uma altura fixa. Para uma altura fixa, apenas aumentaria a largura para caber tudo em uma linha, a menos que houvesse uma quebra de linha no texto.

A função width chama a função height várias vezes, mas é um cálculo rápido e não notei problemas de desempenho usando a função nas linhas de uma 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)
    }
}

1
o que é minimumTextWrapWidth:CGFloat?
Vyachaslav Gerchicov 20/02/19

É apenas um valor inicial para os cálculos na função. Se você espera que a largura seja grande, escolher um pequenoTextWrapWidth mínimo fará com que o loop while passe por iterações adicionais. Portanto, quanto maior a largura mínima, melhor, mas se for maior que a largura real necessária, sempre será a largura retornada.
MSimic 28/05/19

0

Verifique a altura do texto da etiqueta e ela está funcionando nela

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)

            }

0

Esta solução ajudará a calcular a altura e a largura no tempo de execução.

    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)

Aqui você pode calcular a altura estimada que sua string levaria e passá-la para o quadro UILabel.

estimateFrame.Width
estimateFrame.Height 

0

Swift 5:

Se você tem UILabel e, de alguma forma, boundingRect não está funcionando para você (eu enfrentei esse problema. Ele sempre retornava 1 linha de altura.), Há uma extensão para calcular facilmente o tamanho da etiqueta.

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

Você pode usá-lo assim:

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

Funciona para mim


-2
@IBOutlet weak var constraintTxtV: NSLayoutConstraint!
func TextViewDynamicallyIncreaseSize() {
    let contentSize = self.txtVDetails.sizeThatFits(self.txtVDetails.bounds.size)
    let higntcons = contentSize.height
    constraintTxtV.constant = higntcons
}

5
Sua resposta não deve consistir apenas de código, mas também de uma explicação sobre o código. Consulte Como responder para mais detalhes.
MechMK1

Embora esse código possa responder à pergunta, fornecer um contexto adicional a respeito de por que e / ou como esse código responde à pergunta melhora seu valor a longo prazo.
Isma #

Esta resposta está incompleta. Refere-se a variáveis importantes que está tipos são desconhecidos que derrota o propósito
bitwit
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.