Як перевірити, чи UILabel врізаний?


105

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

Як я можу перевірити, чи UILabelє текст обрізаним? Чи можливо це навіть? Зараз я просто перевіряю різницю, залежно від того, в якому режимі я перебуваю, але це не дуже добре.


1
Погляньте на рішення, що базується на підрахунку рядків, які я розмістив тут
Клаус

Не можу повірити, що всі ці роки Apple все ще не включила щось таке базове в UILabelAPI.
funct7

Відповіді:


108

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

NSString UIKit Additions має кілька методів обчислення розміру рядка з певним шрифтом. Однак якщо у вас є мінімальний розмір шрифту для вашої мітки, який дозволяє системі зменшити текст до цього розміру. Ви можете використати sizeWithFont: minFontSize: фактичнийFontSize: forWidth: lineBreakMode: у цьому випадку.

CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}

1
Спасибі, це саме те, що мені було потрібно. Єдина різниця полягала в тому, що розмірWithFont: повертає CGSize.
Рандалл

Ах, дякую, що вказав на це, я виправив зразок коду.
progrmr

16
sizeWithFont - застаріле використання: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}];
Amelia777

2
@fatuhoku numberOfLinesповертає максимальну кількість рядків, які використовуються для відображення тексту, як зазначено у UILabelпосиланні на клас: developer.apple.com/library/ios/documentation/UIKit/Reference/…
Павло

1
якщо мітка має номер рядка, спробуйте помножити ширину на таку кількість рядка, як це, якщо (size.width> label.bounds.size.width * label.numberOfLines) {...}
Fadi Abuzant

89

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

2
приємна відповідь, але краще використовувати обчислену властивість замість func: var isTruncated: Bool {якщо дозволити string = self.text {нехай розмір: CGSize = (рядок як NSString) .boundingRect (з: CGSize (ширина: self.frame.size .width, height: CGFloat.greatestFiniteMagnitude), параметри: NSStringDrawingOptions.usesLineFragmentOrigin, атрибути: [NSFontAttributeName: self.font], контекст: nil) .size return (size.height> self.bounds.size.height)} return false}
Мохаммадаліф

1
Це не спрацювало для мене, оскільки я використовував NSAttributedString. Щоб змусити його працювати, мені потрібно було змінити значення атрибутів для використання: attributedText? .Attributes (at: 0, ефективніRange: nil)
pulse4life

1
Чудова відповідь, довелося трохи змінити його на мої вимоги, але працював на відмінно. Я також використовував атрибутивну рядок - так що для атрибутів, які я використовував: (attributedText? .Attributes (at: 0, ефективніRange: nil) ?? використовуючи це рішення.
Crt Gregoric

20

EDIT: Щойно я побачив, що моя відповідь була анульована, але фрагмент коду, який я дав, застарів.
Тепер найкращий спосіб зробити це (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;
}

3
від doc: textRectForBounds:limitedToNumberOfLines:"Ви не повинні закликати цей метод безпосередньо" ...
Мартін

@Martin спасибі, я бачу, що тут реалізація обмежена. Але коли ви зателефонуєте за цим методом після встановлення меж та тексту, він буде добре працювати
DongXu

Чому ви ставите +1 під час встановлення обмеженихToNumberOfLines?
Ясень

@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

Швидкий 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;
}

Дякую! Використання intrinsicContentSize замість кадру було вирішенням моєї проблеми, коли висота UILabel насправді достатня для розміщення тексту, але вона має обмежену кількість рядків і, таким чином, все ще обрізається.
Антон Матосов

Чомусь це повертає НІ навіть тоді, коли текст не вписується в етикетку
Can Poyrazoğlu

6

Це воно. Це спрацьовує attributedText, перш ніж повернутися до рівняtext , що має багато сенсу для нас, людей, які мають справу з кількома сімействами шрифтів, розмірами та навіть NSTextAttachments!

Добре працює з авторозміткою, але очевидно, що обмеження повинні бути визначені та встановлені перед тим, як перевірити isTruncated, інакше сама ярлик навіть не знає, як викласти себе, так що жоден спосіб навіть не знає, чи буде її усічена.

Вона не працює , щоб підійти до цієї проблеми тільки з рівнинним NSStringі sizeThatFits. Я не впевнений, як люди отримували подібні позитивні результати. BTW, як згадувалося багато разів, використання 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

Чудова відповідь. Єдине питання присвоюєтьсяText ніколи не дорівнює нулю, навіть якщо ви його не встановлюєте. Отже, я реалізував це як два vars isTextTruncated та isAttributedTextTruncated.
Харріс

@Harris він говорить, що за замовчуванням у файлі uilabel.h - нуль
Лукас,

@LucasChwe Я знаю, але це неправда на практиці. Ви можете спробувати це на собі і побачити. fyi, forums.developer.apple.com/thread/118581
Харріс

3

Ось вибрана відповідь у 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
    }
}

Відповідь Акселя для цього не спрацював. Цей і зробив. Дякую!
Гленн

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 та новіших версіях. Сподіваюся, це допомагає! урізання хвоста укола

@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 будь ласка, протестуйте його. Це робочий приклад.
Раз

2
Що робити, якщо числоOfLines дорівнює нулю?
Може Лелоглу

0

Для роботи з iOS 6 (так, дехто з нас все ще повинен), ось ще одне розширення на відповідь @ iDev. Ключовим способом є те, що для iOS 6 переконайтеся, що для вашого UILabel числоOfLines встановлено 0, перш ніж викликати sizeThatFits; якщо ні, то це дасть вам результат, який говорить, що для накреслення тексту мітки потрібні "точки, щоб намалювати числоOfLines варті висоти".

- (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 для встановлення заголовка, якщо довжина більша за ширину діла.

var divlen = parseInt(jQuery("#yourdivid").width,10);
var lablen =jQuery("#yourlabelid").text().length;
if(lablen < divlen){
jQuery("#yourlabelid").attr("title",jQuery("#yourlabelid").text());
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.