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

106

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

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

Randall
источник
1
Взгляните на решение, основанное на количестве строк, которое я разместил здесь
Клаус
Не могу поверить, что после всех этих лет Apple до сих пор не включила что-то столь же простое в UILabelAPI.
funct7

Ответы:

108

Вы можете рассчитать ширину строки и посмотреть, больше ли она, чем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) {
   ...
}
прогрмр
источник
1
Спасибо, это именно то, что мне нужно. Единственная разница заключалась в том, что sizeWithFont: возвращает CGSize.
Randall
Ах, спасибо, что указали на это, я исправил образец кода.
progrmr
16
sizeWithFont не рекомендуется использовать: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}];
Amelia777
2
@fatuhoku numberOfLinesвозвращает максимальное количество строк, используемых для отображения текста, как указано в UILabelсправочнике по классу: developer.apple.com/library/ios/documentation/UIKit/Reference/…
Пол
1
если на этикетке есть количество строк, попробуйте умножить ширину на количество строк, как это if (size.width> label.bounds.size.width * label.numberOfLines) {...}
Фади Абузант
91

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
    }

}
Робин
источник
заменить высоту 999999.0 на CGFloat (FLT_MAX)
ambientlight
2
для swift 3 я бы использовал CGFloat.greatestFiniteMagnitude
zero3nna 01
2
хороший ответ, но лучше использовать вычисляемое свойство вместо 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}
Mohammadalijf
1
У меня это не сработало, потому что я использовал NSAttributedString. Чтобы заставить его работать, мне нужно было изменить значение атрибутов для использования: attributedText? .Attributes (at: 0, effectiveRange: nil)
pulse4life
1
Отличный ответ, пришлось немного изменить его для моих требований, но работал отлично. Я также использовал строку с атрибутами, поэтому для атрибутов, которые я использовал: (attributedText? .Attributes (at: 0, effectiveRange: nil) ?? [.font: font]), просто убедитесь, что вы проверили, не является ли labelText пустым, когда используя это решение.
Crt Gregoric
20

РЕДАКТИРОВАТЬ: Я только что увидел, что за мой ответ проголосовали, но приведенный мной фрагмент кода устарел.
Теперь лучший способ сделать это (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");
}
Мартин
источник
13

вы можете создать категорию с помощью 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;
}
DongXu
источник
3
из документа: textRectForBounds:limitedToNumberOfLines:«Вы не должны вызывать этот метод напрямую» ...
Мартин
@Martin, спасибо, я вижу, что реализация здесь ограничена. Но когда вы вызываете этот метод после установки границ и текста, он будет работать хорошо
DongXu
Почему вы ставите +1 при установке limitedToNumberOfLines?
Эш
@Ash, чтобы проверить, находится ли метка выше, когда для текста разрешено больше места.
vrwim
1
Спасибо за этот код, он работал у меня, за исключением некоторых случаев с границами при использовании автоматического макета. Я исправил их, добавив: setNeedsLayout() layoutIfNeeded()в начале метода.
Йован Йовановски
9

Используйте эту категорию, чтобы узнать, обрезана ли метка в 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
Раджеш
источник
9

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 {

        if (self.countLabelLines() > self.numberOfLines) {
            return true
        }
        return false
    }
}
Клаус
источник
Это хороший ответ для быстрого. Спасибо.
JimmyLee
8

Чтобы добавить к ответу 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;
}
onmyway133
источник
Спасибо! Использование intrinsicContentSize вместо frame было решением моей проблемы, когда высоты UILabel на самом деле достаточно для размещения текста, но он имеет ограниченное количество строк и, таким образом, все еще усекает.
Антон Матосов
По какой-то причине это возвращает NO, даже если текст не помещается на этикетке
Can Poyrazoğlu
6

Это оно. Это работает 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
    }
}
Лукас
источник
это работает нормально для меня спасибо !! есть ли какие-либо обновления или модификации для этого?
amodkanthe
Отличный ответ. Единственная проблема в том, что attributedText никогда не равен нулю, даже если вы его не устанавливаете. Итак, я реализовал это в виде двух переменных: isTextTruncated и isAttributedTextTruncated.
Харрис
@Harris, он говорит, что по умолчанию в файле uilabel.h ноль
Лукас
@LucasChwe Я знаю, но на практике это не так. Вы можете попробовать это сами и убедиться. fyi, forum.developer.apple.com/thread/118581
Харрис
4

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

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
    }
}
Трэвис М.
источник
Ответ Акселя не сработал. Этот сделал. Спасибо!
Гленн,
2

Это работает для 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");
}
Марсио Фонсека
источник
2

Я написал категорию для работы с усечением 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
Алехандро Котилья
источник
каждый раз выдает только NSNotFound
Дари
2
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
    }
}

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

Айк Брсоян
источник
1

Чтобы добавить к тому, что сделал @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;
}
Кгаидис
источник
1

У меня были проблемы с 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
    }
}
Аксель Гильмин
источник
0

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

- (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;
}
Раз
источник
Вы делите ширину на высоту, и если она больше, чем количество строк (что вполне может быть 0), вы говорите, что метка усечена. Это никак не работает.
Кан Лелоглу
@ CanLeloğlu, пожалуйста, проверьте это. Это рабочий пример.
Raz
2
Что, если numberOfLines равно нулю?
Джан Лелоглу,
0

Чтобы справиться с 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);
}
Джон Ячеко
источник
0

Обязательно вызовите любой из них в 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
    }

}
Харрис
источник
0

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 

    }
}
Вэйлан Сэндс
источник
-1

Решение 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
        }
    }
}
Сауд Ризван
источник
-6

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

вы можете рассчитать длину метки и ширину 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());
}
user3405326
источник