Определите размер UILabel на основе строки в Swift

183

Я пытаюсь вычислить высоту UILabel на основе разных длин строк.

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

}

Выше приведена текущая функция, которую я использую для определения высоты, но она не работает. Я был бы очень признателен за любую помощь, которую я могу получить. Я предпочел бы ответ в Swift, а не Цель C.

Коди Уивер
источник
попробуйте этот
файл

Ответы:

519

Используйте расширение на 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)
    }
}

а также на NSAttributedString(что иногда очень полезно)

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

Просто измените значение для attributesв extension Stringметодах

из

[NSFontAttributeName: font]

в

[.font : font]
Каан Дедеоглу
источник
2
@CodyWeaver проверяет редактирование для метода widthWithConstrainedHeight.
Каан Дедеоглу
7
@KaanDedeoglu, как это будет работать со строками динамической высоты, например, когда вы используете "numberOfLines" = 0 (которое может быть специфичным для UILabel, не уверен) или lineBreakMode ByWordWrapping. Я предполагал добавить это к таким атрибутам, [NSFontAttributeName: font, NSLineBreakMode: .ByWordWrapping]но это не сработало
Francisc0
1
Думаю, я понял свой ответ. Мне нужно использовать, NSParagraphStyleAttributeName : styleгде стиль NSMutableParagraphStyle
Francisc0
4
Мне нужно написать «self.boundingRect» вместо «boundingRect» в противном случае я получаю ошибку компиляции.
Майк М
1
Одна вещь , с этим ответом я нашел при использовании его в sizeForItemAtIndexPathв UICollectionViewтом , что он , кажется, отменить возврат кinsetForSectionAt
Zack Шапиро
15

Для многострочного текста этот ответ не работает правильно. Вы можете создать другое расширение String с помощью 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 получает фиксированную ширину, а .numberOfLines устанавливается в 0. Добавляя текст и вызывая .sizeToFit (), он автоматически подстраивается под правильную высоту.

Код написан на Swift 3 🔶🐦

Sn0wfreeze
источник
15
Однако sizeToFit создает миллион проблем с производительностью из-за множества проходов чертежа. Расчет размера вручную намного дешевле в ресурсах
Марко Паппалардо
2
Это решение должно установить UIFont UILabel для обеспечения правильной высоты.
Мэтью Спенсер
отлично работает для меня - даже учитывает пустые строки (в отличие от принятого ответа). Очень удобно для расчета высоты tableView с автоматическими высотами ячеек!
Hendies
Я обнаружил, что принятый ответ работает для фиксированной ширины, но не для фиксированной высоты. Для фиксированной высоты он просто увеличит ширину, чтобы уместить все на одной строке, если в тексте не было разрыва строки. Вот мой альтернативный ответ: Мой ответ
MSimic
2
Я опубликовал подобное решение - без необходимости звонитьsizeToFit
RyanG
6

Вот простое решение, которое работает для меня ... похоже на некоторые другие опубликованные, но оно не включает в себя необходимость звонить sizeToFit

Обратите внимание, что это написано в 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

Это обрабатывает перенос строк и тому подобное. Не самый элегантный код, но он выполняет свою работу.

рианг
источник
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
источник
1

Это мой ответ в Swift 4.1 и 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
}

Теперь вы вызываете эту функцию

//Call this function
let height = heightForView(text: "This is your text", font: UIFont.systemFont(ofSize: 17), width: 300)
print(height)//Output : 41.0
IOS
источник
1

Я обнаружил, что принятый ответ работает для фиксированной ширины, но не для фиксированной высоты. Для фиксированной высоты он просто увеличит ширину, чтобы уместить все на одной строке, если в тексте не было разрыва строки.

Функция width вызывает функцию height несколько раз, но это быстрый расчет, и я не заметил проблем с производительностью при использовании функции в строках 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
источник
1
что такое minimumTextWrapWidth:CGFloat?
Вячеслав Герчиков
Это просто начальное значение для вычислений в функции. Если вы ожидаете, что ширина будет большой, то при выборе небольшого параметраimumTextWrapWidth цикл while будет проходить дополнительные итерации. Таким образом, чем больше минимальная ширина, тем лучше, но если она больше фактической требуемой ширины, то она всегда будет возвращаемой шириной.
MSimic
0

Проверьте высоту текста метки и она работает над этим

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
источник
0

Это решение поможет рассчитать высоту и ширину во время выполнения.

    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)

Здесь вы можете рассчитать приблизительную высоту, которую будет принимать ваша строка, и передать ее в фрейм UILabel.

estimateFrame.Width
estimateFrame.Height 
Шану Сингх
источник
0

Свифт 5:

Если у вас есть UILabel и каким-то образом у вас не работает boundingRect (я сталкивался с этой проблемой. Он всегда возвращал высоту в 1 строку). Существует расширение, позволяющее легко рассчитать размер метки.

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

Вы можете использовать это так:

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

Работает для меня

Георгий Сабанов
источник
-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
Ваш ответ должен состоять не только из кода, но и из объяснения относительно кода. Пожалуйста, смотрите Как ответить для более подробной информации.
МечМК1
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и / или как этот код отвечает на вопрос, повышает его долгосрочную ценность.
Исма
Этот ответ неполон. Это относится к важным переменным, типы которых неизвестны, что побеждает цель
bitwit