Как проверить, усечен ли UILabel?

У меня есть UILabel, длина которого может быть разной в зависимости от того, работает ли мое приложение в портретном или ландшафтном режиме на iPhone или iPad. Когда текст слишком длинный для отображения в одной строке и обрезается, я хочу, чтобы пользователь мог нажать его и получить всплывающее окно с полным текстом.

Как я могу проверить, не усекает ли UILabel текст? Это вообще возможно? Прямо сейчас я просто проверяю разную длину в зависимости от того, в каком режиме я нахожусь, но это не работает очень хорошо.


person Randall    schedule 19.06.2010    source источник


Ответы (21)


Вы можете рассчитать ширину строки и посмотреть, соответствует ли ширина больше чем label.bounds.size.width

NSString UIKit Additions имеет несколько методов для вычисление размера строки с определенным шрифтом. Однако, если у вас есть минимальный размер шрифта для вашей метки, который позволяет системе уменьшить текст до этого размера. Вы можете использовать sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: в этом случае.

CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}
person progrmr    schedule 19.06.2010
comment
Спасибо, это именно то, что мне нужно. Единственная разница заключалась в том, что sizeWithFont: возвращает CGSize. - person Randall; 23.06.2010
comment
Ах, спасибо, что указали на это, я исправил образец кода. - person progrmr; 23.06.2010
comment
sizeWithFont не рекомендуется использовать: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}]; - person Amelia777; 25.07.2014
comment
@fatuhoku numberOfLines возвращает максимальное количество строк, используемых для отображения текста, как указано в ссылке на класс UILabel: developer.apple.com/library/ios/documentation/UIKit/Reference/. - person Paul; 09.05.2016
comment
если метка имеет номер строки, попробуйте умножить ширину на количество строк, как это if (size.width ›label.bounds.size.width * label.numberOfLines) {...} - person Fadi Abuzant; 03.08.2018

Swift (как расширение) - работает для многострочного uilabel:

swift4: (attributes параметр boundingRect немного изменился)

extension UILabel {

    var isTruncated: Bool {

        guard let labelText = text else {
            return false
        }

        let labelTextSize = (labelText as NSString).boundingRect(
            with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
            options: .usesLineFragmentOrigin,
            attributes: [.font: font],
            context: nil).size

        return labelTextSize.height > bounds.size.height
    }
}

swift3:

extension UILabel {

    var isTruncated: Bool {

        guard let labelText = text else { 
            return false
        }

        let labelTextSize = (labelText as NSString).boundingRect(
            with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
            options: .usesLineFragmentOrigin,
            attributes: [NSFontAttributeName: font],
            context: nil).size

        return labelTextSize.height > bounds.size.height
    }
}

swift2:

extension UILabel {

    func isTruncated() -> Bool {

        if let string = self.text {

            let size: CGSize = (string as NSString).boundingRectWithSize(
                CGSize(width: self.frame.size.width, height: CGFloat(FLT_MAX)),
                options: NSStringDrawingOptions.UsesLineFragmentOrigin,
                attributes: [NSFontAttributeName: self.font],
                context: nil).size

            if (size.height > self.bounds.size.height) {
                return true
            }
        }

        return false
    }

}
person Robin    schedule 16.07.2015
comment
заменить высоту 999999.0 на CGFloat (FLT_MAX) - person ambientlight; 25.03.2016
comment
для быстрого 3 я бы использовал CGFloat.greatestFiniteMagnitude - person zero3nna; 01.12.2016
comment
хороший ответ, но лучше использовать вычисляемое свойство вместо func: var isTruncated: Bool {if let string = self.text {let size: CGSize = (string as NSString) .boundingRect (with: CGSize (width: self.frame.size) .width, height: CGFloat.greatestFiniteMagnitude), параметры: NSStringDrawingOptions.usesLineFragmentOrigin, атрибуты: [NSFontAttributeName: self.font], context: nil) .size return (size.height ›self.bounds.size.size.height)} return false} - person Mohammadalijf; 01.06.2017
comment
У меня это не сработало, потому что я использовал NSAttributedString. Чтобы заставить его работать, мне нужно было изменить используемое значение атрибутов: attributedText? .Attributes (at: 0, effectiveRange: nil) - person pulse4life; 06.11.2018
comment
Отличный ответ, пришлось немного изменить его для моих требований, но работал отлично. Я также использовал атрибутивную строку - поэтому для атрибутов, которые я использовал: (attributedText? .Attributes (at: 0, effectiveRange: nil) ?? [.font: font]), просто убедитесь, что вы проверили, не является ли labelText пустым, когда используя это решение. - person Crt Gregoric; 20.12.2018
comment
вам может потребоваться вызвать layoutIfNeeded либо на этикетке, либо в коде isTruncated, в зависимости от того, когда вы проверяете это - person Coreyd303; 19.11.2020

РЕДАКТИРОВАТЬ: Я только что увидел, что за мой ответ проголосовали, но предоставленный мной фрагмент кода устарел.
Теперь лучший способ сделать это - (ARC):

NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = mylabel.lineBreakMode;
NSDictionary *attributes = @{NSFontAttributeName : mylabel.font,
                             NSParagraphStyleAttributeName : paragraph};
CGSize constrainedSize = CGSizeMake(mylabel.bounds.size.width, NSIntegerMax);
CGRect rect = [mylabel.text boundingRectWithSize:constrainedSize
                                         options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                      attributes:attributes context:nil];
if (rect.size.height > mylabel.bounds.size.height) {
    NSLog(@"TOO MUCH");
}

Обратите внимание, что рассчитанный размер не является целым числом. Поэтому, если вы сделаете что-то вроде int height = rect.size.height, вы потеряете некоторую точность с плавающей запятой и можете получить неверные результаты.

Старый ответ (устарел):

Если ваша этикетка многострочная, вы можете использовать этот код:

CGSize perfectSize = [mylabel.text sizeWithFont:mylabel.font constrainedToSize:CGSizeMake(mylabel.bounds.size.width, NSIntegerMax) lineBreakMode:mylabel.lineBreakMode];
if (perfectSize.height > mylabel.bounds.size.height) {
    NSLog(@"TOO MUCH");
}
person Martin    schedule 07.11.2012

вы можете создать категорию с помощью UILabel

- (BOOL)isTextTruncated

{
    CGRect testBounds = self.bounds;
    testBounds.size.height = NSIntegerMax;
    CGRect limitActual = [self textRectForBounds:[self bounds] limitedToNumberOfLines:self.numberOfLines];
    CGRect limitTest = [self textRectForBounds:testBounds limitedToNumberOfLines:self.numberOfLines + 1];
    return limitTest.size.height>limitActual.size.height;
}
person DongXu    schedule 26.10.2012
comment
из документа: textRectForBounds:limitedToNumberOfLines: Вы не должны вызывать этот метод напрямую ... - person Martin; 07.11.2012
comment
@Martin, спасибо, я вижу, что реализация здесь ограничена. Но когда вы вызываете этот метод после установки границ и текста, он будет работать хорошо - person DongXu; 09.11.2012
comment
Почему вы ставите +1 при установке limitedToNumberOfLines? - person Ash; 17.12.2015
comment
@Ash, чтобы проверить, выше ли метка, когда для текста разрешено больше места. - person vrwim; 29.03.2017
comment
Спасибо за этот код, он работал у меня, за исключением некоторых случаев с границами при использовании автоматической компоновки. Я исправил их, добавив: setNeedsLayout() layoutIfNeeded() в начале метода. - person Jovan Jovanovski; 29.08.2018

Swift 3

Вы можете подсчитать количество строк после присвоения строки и сравнить с максимальным количеством строк метки.

import Foundation
import UIKit

extension UILabel {
    
    func countLabelLines() -> Int {
        // Call self.layoutIfNeeded() if your view is uses auto layout
        let myText = self.text! as NSString
        let attributes = [NSFontAttributeName : self.font]
        
        let labelSize = myText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil)
        return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
    }
    
    func isTruncated() -> Bool {
        guard numberOfLines > 0 else { return false }
        return countLabelLines() > numberOfLines
    }
}
person Claus    schedule 24.03.2017
comment
Это хороший ответ для Swift. Спасибо. - person JimmyLee; 26.12.2018

Используйте эту категорию, чтобы узнать, обрезана ли метка в iOS 7 и более поздних версиях.

// UILabel+Truncation.h
@interface UILabel (Truncation)

@property (nonatomic, readonly) BOOL isTruncated;

@end


// UILabel+Truncation.m
@implementation UILabel (Truncation)

- (BOOL)isTruncated
{
    CGSize sizeOfText =
      [self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)
                               options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
                            attributes:@{ NSFontAttributeName : label.font } 
                               context: nil].size;

    if (self.frame.size.height < ceilf(sizeOfText.height))
    {
        return YES;
    }
    return NO;
}

@end
person Rajesh    schedule 01.07.2014

Чтобы добавить в ответ iDev, вы должны использовать intrinsicContentSize вместо frame, чтобы он работал для Autolayout

- (BOOL)isTruncated:(UILabel *)label{
        CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.intrinsicContentSize.width, CGFLOAT_MAX)
                                                     options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                  attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;

        if (self.intrinsicContentSize.height < ceilf(sizeOfText.height)) {
        return YES;
    }
    return NO;
}
person onmyway133    schedule 22.10.2014
comment
Спасибо! Использование intrinsicContentSize вместо frame было решением моей проблемы, когда высоты UILabel на самом деле достаточно, чтобы уместить текст, но он имеет ограниченное количество строк и, таким образом, все еще усекает. - person Anton Matosov; 25.06.2015
comment
По какой-то причине это возвращает NO, даже если текст не помещается в метку. - person Can Poyrazoğlu; 13.03.2017

Это оно. Это работает с attributedText, прежде чем вернуться к простому text, что имеет большой смысл для нас, людей, которые имеют дело с несколькими семействами шрифтов, размерами и даже NSTextAttachments!

Отлично работает с автопложением, но, очевидно, ограничения должны быть определены и установлены до того, как мы проверим isTruncated, иначе сама метка даже не знает, как себя выложить, поэтому она даже не узнает, усечена ли она.

Невозможно подойти к этой проблеме с помощью простых NSString и sizeThatFits. Я не уверен, как люди получали такие положительные результаты. Кстати, как неоднократно упоминалось, использование sizeThatFits совсем не идеально, потому что оно учитывает numberOfLines для результирующего размера, что сводит на нет всю цель того, что мы пытаемся сделать, потому что isTruncated всегда будет возвращать false независимо от того, усечено оно или нет. .

extension UILabel {
    var isTruncated: Bool {
        layoutIfNeeded()

        let rectBounds = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
        var fullTextHeight: CGFloat?

        if attributedText != nil {
            fullTextHeight = attributedText?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, context: nil).size.height
        } else {
            fullTextHeight = text?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil).size.height
        }

        return (fullTextHeight ?? 0) > bounds.size.height
    }
}
person Lucas Chwe    schedule 31.10.2018
comment
это отлично работает для меня, спасибо !! есть ли какие-либо обновления или модификации для этого? - person amodkanthe; 27.08.2019
comment
Отличный ответ. Единственная проблема заключается в том, что attributedText никогда не равен нулю, даже если вы его не устанавливаете. Итак, я реализовал это как две переменные: isTextTruncated и isAttributedTextTruncated. - person Elijah; 01.10.2019
comment
@Harris говорит, что по умолчанию в файле uilabel.h ноль - person Lucas Chwe; 02.10.2019
comment
@LucasChwe Я знаю, но на практике это не так. Вы можете попробовать это сами и убедиться в этом. fyi, forum.developer.apple.com/thread/118581 - person Elijah; 03.10.2019

Вот выбранный ответ в Swift 3 (как расширение). OP спрашивал о метках одной строки. Многие из быстрых ответов, которые я пробовал здесь, относятся к многострочным меткам и не помечают правильно на однострочных метках.

extension UILabel {
    var isTruncated: Bool {
        guard let labelText = text as? NSString else {
            return false
        }
        let size = labelText.size(attributes: [NSFontAttributeName: font])
        return size.width > self.bounds.width
    }
}
person Travis M.    schedule 09.05.2018
comment
Ответ Акселя не сработал. Этот сделал. Спасибо! - person Glenn Posadas; 08.06.2020

Это работает для iOS 8:

CGSize size = [label.text boundingRectWithSize:CGSizeMake(label.bounds.size.width, NSIntegerMax) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : label.font} context:nil].size;

if (size.height > label.frame.size.height) {
    NSLog(@"truncated");
}
person Marcio Fonseca    schedule 17.12.2014

Я написал категорию для работы с усечением UILabel. Работает на iOS 7 и новее. Надеюсь, поможет ! усечение хвоста uilabel

@implementation UILabel (Truncation)

- (NSRange)truncatedRange
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];

    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];

    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
    textContainer.lineFragmentPadding = 0;
    [layoutManager addTextContainer:textContainer];

    NSRange truncatedrange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:0];
    return truncatedrange;
}

- (BOOL)isTruncated
{
    return [self truncatedRange].location != NSNotFound;
}

- (NSString *)truncatedText
{
    NSRange truncatedrange = [self truncatedRange];
    if (truncatedrange.location != NSNotFound)
    {
        return [self.text substringWithRange:truncatedrange];
    }

    return nil;
}

@end
person Alejandro Cotilla    schedule 13.06.2015
comment
каждый раз дает только NSNotFound - person Bibek; 19.09.2018

extension UILabel {

public func resizeIfNeeded() -> CGFloat? {
    guard let text = text, !text.isEmpty else { return nil }

    if isTruncated() {
        numberOfLines = 0
        sizeToFit()
        return frame.height
    }
    return nil
}

func isTruncated() -> Bool {
    guard let text = text, !text.isEmpty else { return false }

    let size: CGSize = text.size(withAttributes: [NSAttributedStringKey.font: font])
    return size.width > self.bounds.size.width
    }
}

Вы можете рассчитать ширину строки и посмотреть, больше ли она, чем ширина этикетки.

person Hayk Brsoyan    schedule 14.05.2018

Чтобы добавить к тому, что сделал @iDev, я изменил self.frame.size.height на использование label.frame.size.height, а также не использовал NSStringDrawingUsesLineFontLeading. После этих изменений я достиг точного расчета того, когда произойдет усечение (по крайней мере, для моего случая).

- (BOOL)isTruncated:(UILabel *)label {
    CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.bounds.size.width, CGFLOAT_MAX)
                                                 options: (NSStringDrawingUsesLineFragmentOrigin)
                                              attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;

    if (label.frame.size.height < ceilf(sizeOfText.height)) {
        return YES;
    }
    return NO;
}
person kgaidis    schedule 09.10.2014

У меня возникли проблемы с boundingRect(with:options:attributes:context:) при использовании автоматического размещения (для установки максимальной высоты ) и текст с атрибутами NSParagraph.lineSpacing

Интервал между строками игнорировался (даже при передаче attributes методу boundingRect), поэтому метка могла считаться не усеченной, когда она была.

Я нашел решение использовать UIView.sizeThatFits:

extension UILabel {
    var isTruncated: Bool {
        layoutIfNeeded()
        let heightThatFits = sizeThatFits(bounds.size).height
        return heightThatFits > bounds.size.height
    }
}
person Axel Guilmin    schedule 04.06.2018

SWIFT 5

Пример многострочного UILabel, который настроен на отображение только 3 строк.

    let labelSize: CGSize = myLabel.text!.size(withAttributes: [.font: UIFont.systemFont(ofSize: 14, weight: .regular)])

    if labelSize.width > myLabel.intrinsicContentSize.width * 3 {
        // your label will truncate
    }

Хотя пользователь может выбрать клавишу возврата, добавив дополнительную строку без увеличения «ширины текста», в этом случае что-то подобное также может быть полезно.

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

    if text == "\n" {
        // return pressed 

    }
}
person Waylan Sands    schedule 12.02.2020

Поскольку во всех приведенных выше ответах используются устаревшие методы, я подумал, что это может быть полезно:

- (BOOL)isLabelTruncated:(UILabel *)label
{
    BOOL isTruncated = NO;

    CGRect labelSize = [label.text boundingRectWithSize:CGSizeFromString(label.text) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : label.font} context:nil];

    if (labelSize.size.width / labelSize.size.height > label.numberOfLines) {

        isTruncated = YES;
    }

    return isTruncated;
}
person Raz    schedule 23.04.2014
comment
Вы делите ширину на высоту, и если она больше, чем количество строк (что вполне может быть 0), вы говорите, что метка усечена. Это никак не работает. - person Can Leloğlu; 23.03.2015
comment
@ CanLeloğlu, пожалуйста, проверьте это. Это рабочий пример. - person Raz; 24.03.2015
comment
Что делать, если numberOfLines равно нулю? - person Can Leloğlu; 27.03.2015

Чтобы справиться с iOS 6 (да, некоторым из нас все же придется), вот еще одно дополнение к ответу @ iDev. Ключевой вывод состоит в том, что для iOS 6 перед вызовом sizeThatFits убедитесь, что numberOfLines вашего UILabel установлено в 0; в противном случае он выдаст результат, в котором говорится, что «точки для рисования имеют высоту, равную numberOfLines», которые необходимы для рисования текста метки.

- (BOOL)isTruncated
{
    CGSize sizeOfText;

    // iOS 7 & 8
    if([self.text respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)])
    {
        sizeOfText = [self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)
                                             options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                          attributes:@{NSFontAttributeName:self.font}
                                             context:nil].size;
    }
    // iOS 6
    else
    {
        // For iOS6, set numberOfLines to 0 (i.e. draw label text using as many lines as it takes)
        //  so that siteThatFits works correctly. If we leave it = 1 (for example), it'll come
        //  back telling us that we only need 1 line!
        NSInteger origNumLines = self.numberOfLines;
        self.numberOfLines = 0;
        sizeOfText = [self sizeThatFits:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)];
        self.numberOfLines = origNumLines;
    }

    return ((self.bounds.size.height < sizeOfText.height) ? YES : NO);
}
person John Jacecko    schedule 30.03.2015

Обязательно вызовите любой из них в viewDidLayoutSubviews.

public extension UILabel {

    var isTextTruncated: Bool {
        layoutIfNeeded()
        return text?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil).size.height ?? 0 > bounds.size.height
    }

    var isAttributedTextTruncated: Bool {
        layoutIfNeeded()
        return attributedText?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size.height ?? 0 > bounds.size.height
    }

}
person Elijah    schedule 30.09.2019

В Swift 5.x

let size = label.text?.size(withAttributes: [NSAttributedString.Key.font: label.font!])
if size!.width > label.bounds.size.width {
    debugPrint("Size increased", size?.width ?? 0, label.bounds.size.width, label.text ?? "")
}
person iOS    schedule 24.06.2021

Решение Swift 3

Я думаю, что лучшим решением будет (1) создать UILabel с теми же свойствами, что и метка, которую вы проверяете на усечение, (2) вызовите .sizeToFit(), (3) сравните атрибуты фиктивного ярлыка с вашим фактическим ярлыком.

Например, если вы хотите проверить, обрезается ли однострочная этикетка разной ширины или нет, вы можете использовать это расширение:

extension UILabel {
    func isTruncated() -> Bool {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: CGFloat.greatestFiniteMagnitude, height: self.bounds.height))
        label.numberOfLines = 1
        label.font = self.font
        label.text = self.text
        label.sizeToFit()
        if label.frame.width > self.frame.width {
            return true
        } else {
            return false
        }
    }
}

... но, опять же, вы можете легко изменить приведенный выше код в соответствии со своими потребностями. Допустим, ваша этикетка многострочная и имеет разную высоту. Тогда расширение будет выглядеть примерно так:

extension UILabel {
    func isTruncated() -> Bool {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        label.numberOfLines = 0
        label.font = self.font
        label.text = self.text
        label.sizeToFit()
        if label.frame.height > self.frame.height {
            return true
        } else {
            return false
        }
    }
}
person Saoud Rizwan    schedule 26.01.2017

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

вы можете рассчитать длину метки и ширину div (преобразовать в длину - jQuery / Javascript - Как преобразовать значение пикселя (20 пикселей) в числовое значение (20)).

установите jquery для установки заголовка, если длина больше ширины div.

var divlen = parseInt(jQuery("#yourdivid").width,10);
var lablen =jQuery("#yourlabelid").text().length;
if(lablen < divlen){
jQuery("#yourlabelid").attr("title",jQuery("#yourlabelid").text());
}
person user3405326    schedule 04.02.2016