Bagaimana cara memeriksa apakah UILabel terpotong?

106

Saya memiliki UILabelyang bisa bervariasi panjangnya tergantung pada apakah aplikasi saya berjalan dalam mode potret atau lanskap di iPhone atau iPad. Ketika teks terlalu panjang untuk ditampilkan pada satu baris dan terpotong, saya ingin pengguna dapat menekannya dan mendapatkan sembulan teks lengkap.

Bagaimana cara memeriksa untuk melihat apakah UILabelteks tersebut dipotong? Apakah itu mungkin? Saat ini saya hanya memeriksa panjang yang berbeda berdasarkan mode apa yang saya gunakan tetapi tidak berfungsi dengan sangat baik.

Randall
sumber
1
Lihat solusi berdasarkan jumlah baris yang saya posting di sini
Claus
Tidak percaya setelah bertahun-tahun Apple masih belum memasukkan sesuatu yang mendasar seperti ini ke dalam UILabelAPI.
funct7

Jawaban:

108

Anda dapat menghitung lebar string dan melihat apakah lebarnya lebih besar darilabel.bounds.size.width

NSString UIKit Additions memiliki beberapa metode untuk menghitung ukuran string dengan font tertentu. Namun, jika Anda memiliki minimumFontSize untuk label Anda yang memungkinkan sistem untuk mengecilkan teks ke ukuran tersebut. Anda mungkin ingin menggunakan sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: dalam kasus tersebut.

CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}
progrmr
sumber
1
Terima kasih, inilah yang saya butuhkan. Satu-satunya perbedaan adalah, sizeWithFont: mengembalikan CGSize.
Randall
Ah, terima kasih telah menunjukkannya, saya telah mengoreksi kode sampelnya.
progrmr
16
sizeWithFont tidak digunakan lagi: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}];
Amelia777
2
@fatuhoku numberOfLinesmengembalikan jumlah baris maksimum yang digunakan untuk menampilkan teks seperti yang diuraikan dalam UILabelreferensi kelas: developer.apple.com/library/ios/documentation/UIKit/Reference/…
Paul
1
jika label memiliki jumlah garis coba kalikan lebar dengan jumlah garis seperti ini jika (size.width> label.bounds.size.width * label.numberOfLines) {...}
Fadi Abuzant
90

Swift (sebagai ekstensi) - berfungsi untuk uilabel multi baris:

swift4: ( attributesparameter boundingRectberubah sedikit)

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
    }

}
Robin
sumber
ganti ketinggian 999999.0 dengan CGFloat (FLT_MAX)
ambientlight
2
untuk swift 3 saya akan menggunakan CGFloat.greatestFiniteMagnitude
zero3nna
2
jawaban yang bagus tetapi lebih baik menggunakan properti terhitung daripada 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), opsi: NSStringDrawingOptions.usesLineFragmentOrigin, atribut: [NSFontAttributeName: self.font], konteks: nil) .size return (size.height> self.bounds.size.height)} return false}
Mohammadalijf
1
Ini tidak berhasil untuk saya karena saya menggunakan NSAttributedString. Untuk membuatnya berfungsi, saya perlu mengubah nilai atribut untuk menggunakan: atributedText? .Attributes (pada: 0, effectiveRange: nil)
pulse4life
1
Jawaban yang bagus, harus mengubahnya sedikit untuk kebutuhan saya, tetapi bekerja dengan sempurna. Saya juga menggunakan string yang diatribusikan - jadi untuk atribut yang saya gunakan: (atributedText? .Attributes (at: 0, effectiveRange: nil) ?? [.font: font]), pastikan untuk memeriksa apakah labelText tidak kosong saat menggunakan solusi ini.
Crt Gregoric
20

EDIT: Saya baru saja melihat jawaban saya diberi suara positif, tetapi cuplikan kode yang saya berikan sudah usang.
Sekarang cara terbaik untuk melakukan ini adalah (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");
}

Perhatikan bahwa ukuran yang dihitung bukanlah nilai integer. Jadi jika Anda melakukan hal-hal seperti itu int height = rect.size.height, Anda akan kehilangan beberapa presisi floating point dan mungkin mendapatkan hasil yang salah.

Jawaban lama (tidak digunakan lagi):

Jika label Anda multiline, Anda dapat menggunakan kode ini:

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");
}
Martin
sumber
13

Anda dapat membuat kategori dengan 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
sumber
3
dari dokter: textRectForBounds:limitedToNumberOfLines:"Anda tidak harus memanggil metode ini secara langsung" ...
Martin
@Martin terima kasih, saya mengerti, implementasinya di sini terbatas. Tetapi ketika Anda memanggil metode ini setelah menyetel batas dan teks, ini akan bekerja dengan baik
DongXu
Mengapa Anda memberi +1 saat menyetel limitedToNumberOfLines?
Ash
@Ash untuk memeriksa apakah label lebih tinggi ketika ada lebih banyak ruang untuk teks.
vrwim
1
Terima kasih untuk kode ini, ini berfungsi untuk saya selain dari beberapa kasus perbatasan saat menggunakan tata letak otomatis. Saya memperbaikinya dengan menambahkan: setNeedsLayout() layoutIfNeeded()di awal metode.
Jovan Jovanovski
9

Gunakan kategori ini untuk mengetahui apakah label terpotong di iOS 7 dan di atasnya.

// 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
Rajesh
sumber
9

Cepat 3

Anda dapat menghitung jumlah baris setelah menetapkan string dan membandingkannya dengan jumlah baris maksimum pada label.

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
    }
}
Claus
sumber
Ini adalah jawaban yang bagus untuk cepat. Terima kasih.
JimmyLee
8

Untuk menambah jawaban iDev , Anda harus menggunakan, intrinsicContentSizebukan frame, agar berfungsi untuk 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
sumber
Terima kasih! Penggunaan intrinsicContentSize sebagai pengganti frame adalah solusi untuk masalah saya ketika tinggi UILabel sebenarnya cukup untuk menyesuaikan teks, tetapi jumlah barisnya terbatas dan dengan demikian masih terpotong.
Anton Matosov
Untuk beberapa alasan, ini mengembalikan TIDAK bahkan ketika teks tidak sesuai dengan label
Can Poyrazoğlu
6

Ini dia. Ini bekerja dengan attributedText, sebelum kembali ke polos text, yang sangat masuk akal bagi kami orang-orang yang berurusan dengan banyak jenis font, ukuran, dan bahkan NSTextAttachments!

Berfungsi dengan baik dengan autolayout, tetapi jelas batasan harus ditentukan dan disetel sebelum kita memeriksa isTruncated, jika tidak label itu sendiri bahkan tidak akan tahu bagaimana menata dirinya sendiri, jadi tidak mungkin ia akan tahu jika terpotong.

Tidak berhasil untuk mendekati masalah ini hanya dengan polos NSStringdan sizeThatFits. Saya tidak yakin bagaimana orang mendapatkan hasil positif seperti itu. BTW, seperti yang disebutkan berkali-kali, menggunakan sizeThatFitstidak ideal sama sekali karena memperhitungkan numberOfLinesukuran yang dihasilkan, yang mengalahkan seluruh tujuan dari apa yang kami coba lakukan, karena isTruncatedakan selalu kembali falseterlepas dari apakah terpotong atau tidak.

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
    }
}
Lucas
sumber
ini berfungsi dengan baik untuk saya terima kasih !! apakah ada pembaruan atau modifikasi untuk ini?
amodkan
Jawaban yang bagus. Satu-satunya masalah adalah atributedText tidak pernah nol, bahkan saat Anda tidak menyetelnya. Jadi, saya menerapkan ini sebagai dua vars isTextTruncated dan isAttributedTextTruncated.
Harris
@Harris dikatakan defaultnya adalah nihil di file uilabel.h
Lucas
@LucasChwe Saya tahu, tapi itu tidak benar dalam praktiknya. Anda dapat mencobanya sendiri dan melihatnya. fyi, forums.developer.apple.com/thread/118581
Harris
4

Inilah jawaban yang dipilih di Swift 3 (sebagai ekstensi). OP menanyakan tentang 1 baris label. Banyak dari jawaban cepat yang saya coba di sini khusus untuk label multi-baris dan tidak ditandai dengan benar pada label baris tunggal.

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
    }
}
Travis M.
sumber
Jawaban Axel tidak berhasil untuk ini. Yang ini berhasil. Terima kasih!
Glenn
2

Ini berfungsi untuk 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");
}
Marcio Fonseca
sumber
2

Saya telah menulis kategori untuk bekerja dengan pemotongan UILabel. Berfungsi di iOS 7 dan lebih baru. Semoga membantu! pemotongan ekor 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
Alejandro Cotilla
sumber
setiap kali memberikan NSNotFound saja
Dari
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
    }
}

Anda dapat menghitung lebar string dan melihat apakah lebarnya lebih besar dari lebar label.

Hayk Brsoyan
sumber
1

Untuk menambah apa yang dilakukan @iDev , saya memodifikasi self.frame.size.heightuntuk menggunakan label.frame.size.heightdan juga tidak menggunakan NSStringDrawingUsesLineFontLeading. Setelah modifikasi tersebut, saya mencapai perhitungan sempurna tentang kapan pemotongan akan terjadi (setidaknya untuk kasus saya).

- (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;
}
kgaidis
sumber
1

Saya mengalami masalah boundingRect(with:options:attributes:context:)saat menggunakan autolayout (untuk menyetel tinggi maksimal) dan teks yang dikaitkan denganNSParagraph.lineSpacing

Jarak antar baris diabaikan (bahkan ketika diteruskan attributeske boundingRectmetode) sehingga label mungkin dianggap tidak terpotong ketika itu.

Solusi yang saya temukan adalah dengan menggunakan UIView.sizeThatFits:

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

Karena semua jawaban di atas menggunakan metode depresiasi, saya pikir ini bisa berguna:

- (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;
}
Raz
sumber
Anda membagi lebar dengan tinggi dan jika lebih besar dari jumlah garis (yang kemungkinan besar adalah 0) Anda mengatakan label terpotong. Tidak mungkin ini berhasil.
Bisakah Leloğlu
@ CanLeloğlu silahkan mengujinya. Ini adalah contoh yang berhasil.
Raz
2
Bagaimana jika numberOfLines sama dengan nol?
Bisakah Leloğlu
0

Untuk menangani iOS 6 (ya, beberapa dari kita masih harus), berikut ekspansi lain untuk jawaban @ iDev. Kuncinya adalah, untuk iOS 6, untuk memastikan numberOfLines UILabel Anda disetel ke 0 sebelum memanggil sizeThatFits; jika tidak, itu akan memberi Anda hasil yang mengatakan "poin untuk menggambar ketinggian numberOfLines" diperlukan untuk menggambar teks label.

- (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);
}
John Jacecko
sumber
0

Pastikan untuk memanggil salah satu dari ini di 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
    }

}
Harris
sumber
0

SWIFT 5

Contoh UILabel dengan banyak baris yang diset untuk menampilkan hanya 3 baris.

    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
    }

Meskipun pengguna dapat memilih tombol kembali dengan menambahkan baris tambahan tanpa menambahkan ke "lebar teks" dalam hal ini sesuatu seperti ini mungkin juga berguna.

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

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

    }
}
Waylan Sands
sumber
-1

Solusi cepat 3

Saya pikir solusi terbaik adalah (1) membuat UILabeldengan properti yang sama dengan label yang Anda periksa pemotongannya, (2) panggilan .sizeToFit(), (3) membandingkan atribut label tiruan dengan label Anda yang sebenarnya.

Misalnya, jika Anda ingin memeriksa apakah satu label berjajar yang memiliki lebar terpotong bervariasi atau tidak, maka Anda dapat menggunakan ekstensi ini:

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

... tetapi sekali lagi, Anda dapat dengan mudah mengubah kode di atas agar sesuai dengan kebutuhan Anda. Jadi katakanlah label Anda memiliki banyak garis dan memiliki tinggi yang bervariasi. Kemudian ekstensi akan terlihat seperti ini:

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
        }
    }
}
Saoud Rizwan
sumber
-6

Bukankah mudah untuk mengatur atribut judul untuk label, pengaturan ini akan menampilkan label lengkap saat diarahkan.

Anda dapat menghitung panjang label dan lebar div (dikonversi ke panjang - jQuery / Javascript - Bagaimana cara mengubah nilai piksel (20px) menjadi nilai angka (20) ).

set jquery untuk menyetel judul jika panjangnya lebih besar dari lebar div.

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