Как получить текст / строку из n-й строки UILabel? - PullRequest
14 голосов
/ 12 декабря 2010

Есть ли простой способ получить (или просто отобразить) текст из заданной строки в UILabel?

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

Я знаю, что это легко сделать с помощью подстроки, но мне нужно знать начальную и конечную точку строки.

В качестве альтернативы я мог бы прокрутить UILabel, если было какое-то смещение к кадру UILabel, и скрыть остальную часть содержимого, которое я не хотел видеть.

Мне не удалось обнаружить ничего, что показывает, как это можно сделать легко. У кого-нибудь есть хорошие идеи?

Спасибо

iphaaw

Ответы [ 10 ]

23 голосов
/ 19 января 2013

У меня есть лучший способ найти его.

Вы можете получить это с помощью CoreText.framework .

1.Добавить CoreText.framework .2.Импорт #import <CoreText/CoreText.h>.Затем используйте следующий метод:

- (NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label {
    NSString *text = [label text];
    UIFont   *font = [label font];
    CGRect    rect = [label frame];

    CTFontRef myFont = CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);
    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];


    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));

    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);

    NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
    NSMutableArray *linesArray = [[NSMutableArray alloc]init];

    for (id line in lines)
    {
        CTLineRef lineRef = (__bridge CTLineRef )line;
        CFRange lineRange = CTLineGetStringRange(lineRef);
        NSRange range = NSMakeRange(lineRange.location, lineRange.length);

        NSString *lineString = [text substringWithRange:range];
        [linesArray addObject:lineString];
    }

    return (NSArray *)linesArray;
}

Вызовите этот метод: -

NSArray *linesArray = [self getLinesArrayOfStringInLabel:yourLabel];

Теперь вы можете использовать linesArray.

SWIFT 4 VERSION

func getLinesArrayOfString(in label: UILabel) -> [String] {

        /// An empty string's array
        var linesArray = [String]()

        guard let text = label.text, let font = label.font else {return linesArray}

        let rect = label.frame

        let myFont: CTFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value: myFont, range: NSRange(location: 0, length: attStr.length))

        let frameSetter: CTFramesetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path: CGMutablePath = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: rect.size.width, height: 100000), transform: .identity)

        let frame: CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
        guard let lines = CTFrameGetLines(frame) as? [Any] else {return linesArray}

        for line in lines {
            let lineRef = line as! CTLine
            let lineRange: CFRange = CTLineGetStringRange(lineRef)
            let range = NSRange(location: lineRange.location, length: lineRange.length)
            let lineString: String = (text as NSString).substring(with: range)
            linesArray.append(lineString)
        }
        return linesArray
 }

Использование:

let lines: [String] = getLinesArrayOfString(in: label)
8 голосов
/ 30 июня 2015

Свифт 3

func getLinesArrayFromLabel(label:UILabel) -> [String] {

        let text:NSString = label.text! as NSString // TODO: Make safe?
        let font:UIFont = label.font
        let rect:CGRect = label.frame

        let myFont:CTFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
        let attStr:NSMutableAttributedString = NSMutableAttributedString(string: text as String)
        attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSMakeRange(0, attStr.length))
        let frameSetter:CTFramesetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path:CGMutablePath = CGMutablePath()
        path.addRect(CGRect(x:0, y:0, width:rect.size.width, height:100000))

        let frame:CTFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
        let lines = CTFrameGetLines(frame) as NSArray
        var linesArray = [String]()

        for line in lines {
            let lineRange = CTLineGetStringRange(line as! CTLine)
            let range:NSRange = NSMakeRange(lineRange.location, lineRange.length)
            let lineString = text.substring(with: range)
            linesArray.append(lineString as String)
        }
        return linesArray
}

Версия Swift 2 (Xcode 7) (протестировано и отредактировано из ответа Swift 1)

func getLinesArrayOfStringInLabel(label:UILabel) -> [String] {

let text:NSString = label.text! // TODO: Make safe?
let font:UIFont = label.font
let rect:CGRect = label.frame

let myFont:CTFontRef = CTFontCreateWithName(font.fontName, font.pointSize, nil)
let attStr:NSMutableAttributedString = NSMutableAttributedString(string: text as String)
attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSMakeRange(0, attStr.length))
let frameSetter:CTFramesetterRef = CTFramesetterCreateWithAttributedString(attStr as CFAttributedStringRef)
let path:CGMutablePathRef = CGPathCreateMutable()
CGPathAddRect(path, nil, CGRectMake(0, 0, rect.size.width, 100000))
let frame:CTFrameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
let lines = CTFrameGetLines(frame) as NSArray
var linesArray = [String]()

for line in lines {
    let lineRange = CTLineGetStringRange(line as! CTLine)
    let range:NSRange = NSMakeRange(lineRange.location, lineRange.length)
    let lineString = text.substringWithRange(range)
    linesArray.append(lineString as String)
}
return linesArray
}
5 голосов
/ 19 февраля 2013

Ответ с правильным выпуском !!!!

-(NSArray *)getLinesArrayOfStringInLabel:(UILabel *)label
{
    NSString *text = [label text];
    UIFont   *font = [label font];
    CGRect    rect = [label frame];

    CTFontRef myFont = CTFontCreateWithName(( CFStringRef)([font fontName]), [font pointSize], NULL);
    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
    [attStr addAttribute:(NSString *)kCTFontAttributeName value:( id)myFont range:NSMakeRange(0, attStr.length)];

    CFRelease(myFont);

    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(( CFAttributedStringRef)attStr);

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,100000));

    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);

    NSArray *lines = ( NSArray *)CTFrameGetLines(frame);

    NSMutableArray *linesArray = [[NSMutableArray alloc]init];

    for (id line in lines)
    {
        CTLineRef lineRef = ( CTLineRef )line;
        CFRange lineRange = CTLineGetStringRange(lineRef);
        NSRange range = NSMakeRange(lineRange.location, lineRange.length);

        NSString *lineString = [text substringWithRange:range];

        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithFloat:0.0]));
        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attStr, lineRange, kCTKernAttributeName, (CFTypeRef)([NSNumber numberWithInt:0.0]));

        //NSLog(@"''''''''''''''''''%@",lineString);
        [linesArray addObject:lineString];

    }
    [attStr release];

    CGPathRelease(path);
    CFRelease( frame );
    CFRelease(frameSetter);


    return (NSArray *)linesArray;
}
4 голосов
/ 12 декабря 2010

Я не думаю, что есть собственный способ сделать это (например, метод "takethenline").
Я могу найти хитрое решение, но я не уверен, что это лучшее.
Вы могли быразделите вашу метку на массив слов.
Затем вы можете зациклить массив и проверять высоту текста до тех пор, пока это слово не будет выглядеть следующим образом:

NSString *texttocheck;
float old_height = 0;
int linenumber = 0; 

for (x=0; x<[wordarray lenght]; x++) {
    texttocheck = [NSString stringWithFormat:@"%@ %@", texttocheck, [wordarray objectAtIndex:x]];

    float height = [text sizeWithFont:textLabel.font
                    constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
                        lineBreakMode:UILineBreakModeWordWrap].height;

    if (old_height < height) {
        linenumber++;
    }
}

Если высота изменяется, это означает, что перед строкой есть разрыв строкиword.
Я не могу проверить, правильно ли сейчас написан синтаксис, поэтому вы должны проверить его сами.

2 голосов
/ 14 декабря 2018

Очень важное изменение в отношении iOS 11 +

Начиная с iOS 11, Apple преднамеренно изменила поведение своей функции переноса слов для UILabel, которая влияетобнаружение String содержимого отдельных строк в многострочном UILabel.По замыслу перенос слов UILabel теперь позволяет избежать потерянного текста (отдельных слов в новой строке), как обсуждалось здесь: перенос слов в iOS 11

Из-за этогоспособ, которым CTFrameGetLines(frame) возвращает массив CTLine всех строк в метке, больше не работает должным образом, если в определенной строке вступает в силу новая переносимость слов, которая позволяет избежать потерянного текста.Наоборот, это приводит к тому, что в некоторых частях String новый дизайн переноса слов будет принадлежать следующей строке, а не попадать в фокус в строке.

Можно найти проверенное исправление для этой проблемы.в моей измененной версии @ TheTiger's answer , в которой используется расчет фактического размера содержимого UILabel с использованием sizeThatFits(size:), перед использованием этого размера для создания прямоугольника / пути, записанного в Swift4 :

extension UILabel {

    /// creates an array containing one entry for each line of text the label has
    var lines: [String]? {

        guard let text = text, let font = font else { return nil }

        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: attStr.length))

        let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path = CGMutablePath()

        // size needs to be adjusted, because frame might change because of intelligent word wrapping of iOS
        let size = sizeThatFits(CGSize(width: self.frame.width, height: .greatestFiniteMagnitude))
        path.addRect(CGRect(x: 0, y: 0, width: size.width, height: size.height), transform: .identity)

        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, nil)
        guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }

        var linesArray: [String] = []

        for line in lines {
            let lineRef = line as! CTLine
            let lineRange = CTLineGetStringRange(lineRef)
            let range = NSRange(location: lineRange.location, length: lineRange.length)
            let lineString = (text as NSString).substring(with: range)
            linesArray.append(lineString)
        }
        return linesArray
    }
}

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

2 голосов
/ 07 апреля 2017

Это версия Swift 3 для получения всех строк на этикетке.(у @fredpi похожий ответ, но только для первой строки)

extension UILabel {

    func getArrayOfLinesInLabel() -> [String] {

       let text = NSString(string: self.text ?? "-- -- -- --")
       let font = self.font ?? // Your default font here
       let rect = self.frame

       let myFont = CTFontCreateWithName(font.fontName as CFString?, font.pointSize, nil)
       let attStr = NSMutableAttributedString(string: text as String)
       attStr.addAttribute(String(kCTFontAttributeName), value:myFont, range: NSRange(location: 0, length: attStr.length))
       let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
       let path = CGPath(rect: CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height), transform: nil)
       let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
       guard let lines = CTFrameGetLines(frame) as? [CTLine] else {
           return []
       }

       var linesArray = [String]()

       for line in lines {
           let lineRange = CTLineGetStringRange(line)
           let range = NSRange(location: lineRange.location, length: lineRange.length)
           let lineString = text.substring(with: range)
           linesArray.append(lineString as String)
       }

       return linesArray
   }
}
1 голос
/ 06 ноября 2016

Swift 3 - Xcode 8.1

Я собрал код из предыдущих ответов для создания Swift 3, Xcode 8.1-совместимого extension в UILabel, возвращая первоестрока метки.

import CoreText

extension UILabel {

   /// Returns the String displayed in the first line of the UILabel or "" if text or font is missing
   var firstLineString: String {

    guard let text = self.text else { return "" }
    guard let font = self.font else { return "" }
    let rect = self.frame

    let attStr = NSMutableAttributedString(string: text)
    attStr.addAttribute(String(kCTFontAttributeName), value: CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil), range: NSMakeRange(0, attStr.length))

    let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
    let path = CGMutablePath()
    path.addRect(CGRect(x: 0, y: 0, width: rect.size.width + 7, height: 100))
    let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)

    guard let line = (CTFrameGetLines(frame) as! [CTLine]).first else { return "" }
    let lineString = text[text.startIndex...text.index(text.startIndex, offsetBy: CTLineGetStringRange(line).length-2)]

    return lineString
  }
}

Чтобы использовать ее, просто вызовите firstLineString на вашем UILabel экземпляре, например:

let firstLine = myLabel.firstLineString
0 голосов
/ 31 мая 2019

Извините, моя репутация слишком низкая, чтобы оставлять комментарии.Это комментарий https://stackoverflow.com/a/53783203/2439941 от Philipp Jahoda.

Ваш фрагмент кода работал безупречно, пока мы не включили Dynamic Type в UILabel.Когда мы установили размер текста на самое большое значение в приложении «Настройки iOS», оно начало пропускать символы в последней строке возвращаемого массива.Или даже пропустить последнюю строку полностью со значительным количеством текста.

Нам удалось решить эту проблему, используя другой способ получить frame:

let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: .greatestFiniteMagnitude))
let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path.cgPath, nil)
guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }

Теперь он работает правильно длялюбой размер динамического типа.

Полная функция тогда:

extension UILabel {

    /// creates an array containing one entry for each line of text the label has
    var lines: [String]? {

        guard let text = text, let font = font else { return nil }

        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: attStr.length))

        let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: self.frame.width, height: .greatestFiniteMagnitude))
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path.cgPath, nil)
        guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil }

        var linesArray: [String] = []

        for line in lines {
            let lineRef = line as! CTLine
            let lineRange = CTLineGetStringRange(lineRef)
            let range = NSRange(location: lineRange.location, length: lineRange.length)
            let lineString = (text as NSString).substring(with: range)
            linesArray.append(lineString)
        }
        return linesArray
    }
}
0 голосов
/ 14 марта 2019

Ответ принят очень хороший.

Я рефакторил два места:

  1. изменено 10000 на CGFloat.greatestFiniteMagnitude

  2. Добавлен в extension из UILabel

  3. Я также хочу упомянуть, если вы создаете метку, устанавливая рамку, она работает нормально. Если вы используете autolayout, то не забывайте звонить

    youLabel.layoutIfNeeded()

чтобы получить правильный размер кадра.

Вот код:

extension UILabel {
    var stringLines: [String] {
        guard let text = text, let font = font else { return [] }
        let ctFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value: ctFont, range: NSRange(location: 0, length: attStr.length))
        let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude), transform: .identity)
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
        guard let lines = CTFrameGetLines(frame) as? [Any] else { return [] }
        return lines.map { line in
            let lineRef = line as! CTLine
            let lineRange: CFRange = CTLineGetStringRange(lineRef)
            let range = NSRange(location: lineRange.location, length: lineRange.length)
            return (text as NSString).substring(with: range)
        }
    }
}
0 голосов
/ 28 марта 2011

Если все ваши персонажи отображаются в одинаковом размере, то есть они заключены в коробку общего размера, вы можете использовать это. (Это похоже на случай с японскими символами, например.)

В противном случае вы можете запросить размер каждого символа в отображаемом шрифте и рассчитать, какой должна быть строка.

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

(Возможно, я делал это неправильно, но я не нашел метод Apple, приведенный в документации, очень точным, поэтому я сам сделал что-то еще.)

...