Déterminez la taille d'UILabel basée sur String dans Swift


183

J'essaie de calculer la hauteur d'un UILabel en fonction de différentes longueurs de chaîne.

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

}

Ci-dessus se trouve la fonction actuelle que j'utilise pour déterminer la hauteur mais cela ne fonctionne pas. J'apprécierais grandement toute aide que je peux obtenir. Je préférerais la réponse en Swift et non en Objective C.


Réponses:


519

Utiliser une extension sur 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)
    }
}

et aussi sur NSAttributedString(ce qui est parfois très utile)

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

Changez simplement la valeur pour attributesdans les extension Stringméthodes

de

[NSFontAttributeName: font]

à

[.font : font]

2
@CodyWeaver vérifie la modification de la méthode widthWithConstrainedHeight.
Kaan Dedeoglu

7
@KaanDedeoglu comment cela fonctionnerait-il avec des chaînes de hauteur dynamiques comme lorsque vous utilisez "numberOfLines" = 0 (qui peut être spécifique à UILabel, pas sûr) ou lineBreakMode ByWordWrapping. Je suppose que c'était d'ajouter cela aux attributs comme celui-ci, [NSFontAttributeName: font, NSLineBreakMode: .ByWordWrapping]mais cela n'a pas fonctionné
Francisc0

1
Je pense avoir compris ma réponse. J'ai besoin d'utiliser NSParagraphStyleAttributeName : styleoù le style est NSMutableParagraphStyle
Francisc0

4
J'ai besoin d'écrire «self.boundingRect» au lieu de «boundingRect» sinon j'obtiens une erreur de compilation.
Mike M

1
Une chose avec cette réponse que j'ai trouvée en l'utilisant sizeForItemAtIndexPathdans un UICollectionViewest qu'il semble annuler le retour pourinsetForSectionAt
Zack Shapiro

15

Pour le texte multiligne, cette réponse ne fonctionne pas correctement. Vous pouvez créer une extension de chaîne différente en utilisant 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
 }
}

Le UILabel obtient une largeur fixe et le .numberOfLines est mis à 0. En ajoutant le texte et en appelant .sizeToFit (), il s'ajuste automatiquement à la hauteur correcte.

Le code est écrit en Swift 3 🔶🐦


15
sizeToFit introduit cependant un million de problèmes de performances en raison des nombreuses passes du dessin. Calculer la taille manuellement est beaucoup moins cher en ressources
Marco Pappalardo

2
Cette solution doit définir l'UIFont de l'UILabel pour garantir la bonne hauteur.
Matthew Spencer

fonctionne parfaitement pour moi - tient même compte des chaînes vides (contrairement à la réponse acceptée). Très pratique pour calculer la hauteur d'une tableView avec des cellules de hauteur automatiques!
Hendies

J'ai trouvé que la réponse acceptée fonctionnait pour une largeur fixe, mais pas pour une hauteur fixe. Pour une hauteur fixe, cela augmenterait simplement la largeur pour tout ranger sur une seule ligne, sauf s'il y avait un saut de ligne dans le texte. Voici ma réponse alternative: Ma réponse
MSimic

2
J'ai posté une solution similaire - sans avoir besoin d'un appel àsizeToFit
RyanG

6

Voici une solution simple qui fonctionne pour moi ... similaire à certaines des autres publiées, mais elle n'inclut pas la nécessité d'appeler sizeToFit

Notez que ceci est écrit dans 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

Cela gère l'enroulement de ligne et autres. Ce n'est pas le code le plus élégant, mais il fait le travail.


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

C'est ma réponse dans Swift 4.1 et 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
}

Maintenant, vous appelez cette fonction

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

1

J'ai trouvé que la réponse acceptée fonctionnait pour une largeur fixe, mais pas pour une hauteur fixe. Pour une hauteur fixe, cela augmenterait simplement la largeur pour tout ranger sur une seule ligne, sauf s'il y avait un saut de ligne dans le texte.

La fonction width appelle la fonction height plusieurs fois, mais c'est un calcul rapide et je n'ai pas remarqué de problèmes de performances en utilisant la fonction dans les lignes d'un 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
qu'est ce que c'est minimumTextWrapWidth:CGFloat?
Vyachaslav Gerchicov

Il s'agit simplement d'une valeur de départ pour les calculs de la fonction. Si vous vous attendez à ce que la largeur soit grande, la sélection d'un petit minimumTextWrapWidth fera passer la boucle while à des itérations supplémentaires. Donc, plus la largeur minimale est grande, mieux c'est, mais si elle est plus grande que la largeur réelle requise, alors ce sera toujours la largeur renvoyée.
MSimic

0

Vérifiez la hauteur du texte de l'étiquette et il y travaille

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

Cette solution aidera à calculer la hauteur et la largeur lors de l'exécution.

    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)

Ici, vous pouvez calculer la hauteur estimée que prendrait votre chaîne et la transmettre au cadre UILabel.

estimateFrame.Width
estimateFrame.Height 

0

Swift 5:

Si vous avez UILabel et que le boundingRect ne fonctionne pas pour vous (j'ai rencontré ce problème. Il a toujours renvoyé 1 hauteur de ligne.) Il y a une extension pour calculer facilement la taille de l'étiquette.

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

Vous pouvez l'utiliser comme ceci:

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

Travaille pour moi


-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
Votre réponse ne doit pas seulement consister en un code, mais également en une explication concernant le code. Veuillez consulter Comment répondre pour plus de détails.
MechMK1

Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire concernant la raison et / ou la manière dont ce code répond à la question améliore sa valeur à long terme.
Isma

Cette réponse est incomplète. Il fait référence à des variables importantes dont les types sont inconnus, ce qui va à l'
encontre
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.