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

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

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

Ответ 1

Вы можете рассчитать ширину строки и посмотреть, больше ли ширина 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) {
   ...
}

Ответ 2

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
    }

}

Ответ 3

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

Ответ 4

вы можете создать категорию с 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;
}

Ответ 5

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

Ответ 6

Чтобы добавить к 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;
}

Ответ 7

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

Ответ 8

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

Прекрасно работает с autolayout, но очевидно, что ограничения должны быть определены и установлены до того, как мы проверим 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
    }
}

Ответ 9

Это работает для 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");
}

Ответ 10

Я написал категорию для работы с усечением 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

Ответ 11

Здесь выбран ответ в Swift 3 (как расширение). ОП спрашивал о метках в 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
    }
}

Ответ 12

Чтобы добавить к @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;
}

Ответ 13

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

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

Ответ 14

У меня были проблемы с 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
    }
}

Ответ 15

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

- (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;
}

Ответ 16

Чтобы обрабатывать iOS 6 (да, некоторые из нас все еще должны), здесь еще одно расширение для ответа @iDev. Ключевым выводом является то, что для iOS 6 убедитесь, что ваш UILabel numberOfLines установлен в 0 перед вызовом sizeThatFits; если нет, это даст вам результат, в котором говорится, что для рисования текста метки необходимо нарисовать число, необходимое для рисования 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);
}

Ответ 17

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

}

Ответ 18

Решение 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
        }
    }
}

Ответ 19

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

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