Oblicz rozmiar UILabel na podstawie String w Swift

182

Próbuję obliczyć wysokość UILabel na podstawie różnych długości ciągu.

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

}

Powyżej znajduje się bieżąca funkcja, której używam do określenia wysokości, ale nie działa. Byłbym bardzo wdzięczny za każdą pomoc, którą mogę uzyskać. Odpowiedziałbym na pytanie w Szybkim, a nie w Celu C.

Cody Weaver
źródło
zduplikuj

Odpowiedzi:

517

Użyj rozszerzenia na String

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

a także na NSAttributedString(co czasami jest bardzo przydatne)

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

Szybki 4

Po prostu zmień wartość attributesw extension Stringmetodach

z

[NSFontAttributeName: font]

do

[.font : font]
Kaan Dedeoglu
źródło
2
@CodyWeaver sprawdź edycję metody widthWithConstrainedHeight.
Kaan Dedeoglu
7
@KananDedeoglu jak to zadziałałoby z dynamicznymi łańcuchami wysokości takimi jak przy użyciu „numberOfLines” = 0 (co może być specyficzne dla UILabel, nie jestem pewien) lub lineBreakMode ByWordWrapping. Przypuszczam, że dodam to do takich atrybutów, [NSFontAttributeName: font, NSLineBreakMode: .ByWordWrapping]ale to nie zadziałało
Francisc0,
1
Chyba wymyśliłem odpowiedź. Muszę użyć, gdy NSParagraphStyleAttributeName : stylestyl to NSMutableParagraphStyle
Francisc0
4
Muszę napisać „self.boundingRect” zamiast „boundingRect”, w przeciwnym razie pojawia się błąd kompilacji.
Mike M
1
Jedna rzecz z tej odpowiedzi, jakie stwierdzono podczas używania go w sizeForItemAtIndexPathw sposób UICollectionViewjest to, że wydaje się, aby zastąpić powrót doinsetForSectionAt
Zack Shapiro
15

W przypadku tekstu wielowierszowego ta odpowiedź nie działa poprawnie. Możesz zbudować inne rozszerzenie String za pomocą 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
 }
}

UILabel otrzymuje stałą szerokość, a .numberOfLines jest ustawiony na 0. Po dodaniu tekstu i wywołaniu .sizeToFit () automatycznie dostosowuje się do właściwej wysokości.

Kod jest napisany w Swift 3 🔶🐦

Sn0wfreeze
źródło
15
sizeToFit wprowadza jednak milion problemów z wydajnością ze względu na wiele przebiegów rysunku. Ręczne obliczanie rozmiaru jest znacznie tańsze w zasobach
Marco Pappalardo
2
To rozwiązanie powinno ustawić UIFont UILabel, aby zapewnić prawidłową wysokość.
Matthew Spencer
działa dla mnie idealnie - nawet uwzględnia puste ciągi znaków (w przeciwieństwie do przyjętej odpowiedzi). Bardzo przydatny do obliczania wysokości stołuWidok z automatycznymi komórkami wysokości!
Hendies
Odkryłem, że zaakceptowana odpowiedź działała dla stałej szerokości, ale nie dla stałej wysokości. W przypadku stałej wysokości zwiększyłoby szerokość, aby dopasować wszystko do jednego wiersza, chyba że w tekście występował podział linii. Oto moja alternatywna odpowiedź: Moja odpowiedź
MSimic
2
sizeToFit
Podałem
6

Oto proste rozwiązanie, które działa dla mnie ... podobnie jak niektóre inne opublikowane, ale nie obejmuje potrzeby dzwonienia sizeToFit

Pamiętaj, że jest to napisane w 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

To obsługuje owijanie linii i tym podobne. Nie jest to najbardziej elegancki kod, ale wykonuje zadanie.

RyanG
źródło
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
źródło
1

Oto moja odpowiedź w Swift 4.1 i 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
}

Teraz wywołujesz tę funkcję

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

Odkryłem, że zaakceptowana odpowiedź działała dla stałej szerokości, ale nie dla stałej wysokości. W przypadku stałej wysokości zwiększyłoby szerokość, aby dopasować wszystko do jednego wiersza, chyba że w tekście występował podział linii.

Funkcja width wywołuje funkcję wysokości wiele razy, ale jest to szybkie obliczenie i nie zauważyłem problemów z wydajnością podczas używania funkcji w wierszach 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
źródło
1
co jest minimumTextWrapWidth:CGFloat?
Vyachaslav Gerchicov
Jest to tylko wartość początkowa dla obliczeń w funkcji. Jeśli spodziewasz się, że szerokość będzie duża, wybranie małego minimumTextWrapWidth spowoduje przejście przez pętlę while dodatkowych iteracji. Im większa minimalna szerokość, tym lepiej, ale jeśli jest ona większa niż rzeczywista wymagana szerokość, to zawsze będzie zwracana szerokość.
MSimic
0

Sprawdź wysokość tekstu etykiety i na nim działa

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
źródło
0

To rozwiązanie pomoże obliczyć wysokość i szerokość w czasie wykonywania.

    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)

Tutaj możesz obliczyć szacunkową wysokość, jaką wziąłby Twój łańcuch i przekazać go do ramy UILabel.

estimateFrame.Width
estimateFrame.Height 
Shanu Singh
źródło
0

Swift 5:

Jeśli masz UILabel, a jakaś funkcja ograniczania nie działa dla ciebie (napotkałem ten problem. Zawsze zwracała 1 linię wysokości), istnieje rozszerzenie ułatwiające obliczenie rozmiaru etykiety.

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

Możesz użyć tego w następujący sposób:

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

Pracuje dla mnie

George Sabanov
źródło
-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
źródło
5
Twoja odpowiedź powinna zawierać nie tylko kod, ale także wyjaśnienie dotyczące kodu. Aby uzyskać więcej informacji, zobacz Jak odpowiedzieć .
MechMK1,
Chociaż ten kod może odpowiedzieć na pytanie, zapewnienie dodatkowego kontekstu dotyczącego tego, dlaczego i / lub jak ten kod odpowiada na pytanie, poprawia jego długoterminową wartość.
Isma
Ta odpowiedź jest niepełna. Odnosi się do ważnych zmiennych, których typy są nieznane, co pokonuje cel
bitwit