How to get text / String from nth line of UILabel?

前端 未结 10 1626
孤街浪徒
孤街浪徒 2020-11-27 05:27

Is there an easy way to get (or simply display) the text from a given line in a UILabel?

My UILabel is correctly displaying my text and laying it out beautifully bu

相关标签:
10条回答
  • 2020-11-27 05:29

    Sorry, my reputation is too low to place a comment. This is a comment to https://stackoverflow.com/a/53783203/2439941 from Philipp Jahoda.

    Your code snippet worked flawless, until we enabled Dynamic Type on the UILabel. When we set the text size to the largest value in the iOS Settings app, it started to miss characters in the last line of the returned array. Or even missing the last line completely with a significant amount of text.

    We managed to resolve this by using a different way to get 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 }
    

    Now it works correctly for any Dynamic Type size.

    The complete function is then:

    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 讨论(0)
  • 2020-11-27 05:31

    Swift 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) version (tested, and re-edited from the Swift 1 answer)

    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
    }
    
    0 讨论(0)
  • 2020-11-27 05:31

    The accepted answer is very good.

    I refactored two places:

    1. changed 10000 to CGFloat.greatestFiniteMagnitude

    2. Added it to an extension of UILabel

    3. I also want to mention, if you create the label by setting the frame it works fine. If you use autolayout then dont forgot to call

      youLabel.layoutIfNeeded()

    to get correct frame size.

    Here is the code:

    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 讨论(0)
  • 2020-11-27 05:34

    Swift 3 – Xcode 8.1

    I've put together code from the previous answers to create a Swift 3, Xcode 8.1-compatible extension to UILabel returning the first line of the label.

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

    To use it, simple call firstLineString on your UILabel instance like this:

    let firstLine = myLabel.firstLineString
    
    0 讨论(0)
  • 2020-11-27 05:39

    This is the Swift 3 version for getting all the lines in the label. (@fredpi has a similar answer but it's only for the first line)

    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
       }
    }
    
    0 讨论(0)
  • 2020-11-27 05:40

    I have better way to find it.

    You can get this with the help of CoreText.framework.

    1.Add CoreText.framework.
    2.Import #import <CoreText/CoreText.h>.
    Then use below method:

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

    Call this method :-

    NSArray *linesArray = [self getLinesArrayOfStringInLabel:yourLabel];
    

    Now you can use 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 = CTFontCreateWithFontDescriptor(font.fontDescriptor, 0, 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
     }
    

    Use:

    let lines: [String] = getLinesArrayOfString(in: label)
    
    0 讨论(0)
提交回复
热议问题