【问题标题】:How to get text / String from nth line of UILabel?如何从 UILabel 的第 n 行获取文本/字符串?
【发布时间】:2011-05-24 04:54:30
【问题描述】:

有没有一种简单的方法可以从 UILabel 中的给定行获取(或简单地显示)文本?

我的 UILabel 正确地显示了我的文本并把它布置得很漂亮,但有时我需要能够只显示某些行,但显然我需要知道 UILabel 是如何定位所有内容来做到这一点的。

我知道这可以通过子字符串轻松完成,但我需要知道该行的起点和终点。

或者,如果 UILabel 的框架有某种偏移,我可以滚动 UILabel 并隐藏我不想看到的其余内容。

我无法发现任何可以轻松完成此操作的内容。有人有什么好主意吗?

谢谢

iphaaw

【问题讨论】:

    标签: ios swift uilabel core-text


    【解决方案1】:

    我认为没有本地方法可以做到这一点(例如“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++;
        }
    }
    

    如果高度发生变化,则表示单词前有换行符。
    我现在无法检查语法是否正确,所以你必须自己检查。

    【讨论】:

    • 如果文本包含换行符\n,这将不起作用
    • 查看我将近 9 年的旧代码,我假设通过将字符串拆分为单词,您缺少 \n 特殊字符。如果我必须解决这个问题,我可能会尝试确保字符包含在 wordarray 中。
    【解决方案2】:

    如果你的所有角色都以相同的大小显示,即它们被封闭在一个相同大小的盒子中,你可以利用它。 (例如,日语字符似乎就是这种情况。)

    否则,您可以查询显示字体中每个字符的大小并计算行的大小。

    唯一担心的是,您的计算可能与 Apple 在幕后所做的不一致 - 在这种情况下,我建议您尝试覆盖文本框架绘图。在文档中查找核心文本。

    (我可能做错了,但我没有发现文档中给出的 Apple 方法非常准确,所以我自己做了其他事情。)

    【讨论】:

      【解决方案3】:

      我有更好的方法找到它。

      您可以在CoreText.framework 的帮助下获得此信息。

      1.添加CoreText.framework.
      2.导入#import &lt;CoreText/CoreText.h&gt;
      然后使用下面的方法:

      - (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 版本

      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
       }
      

      用途:

      let lines: [String] = getLinesArrayOfString(in: label)
      

      【讨论】:

      • SWIFT 版本?
      • @SazzadHissainKhan Swift 版本现已推出。
      • 此函数在某些情况下会失败,因为标签中的文本会被截断。如果发生这种情况,则这些行没有正确添加到数组中,因为该行的最后一个单词被移动到之前的行:/
      • @MatíasContrerasSelman 感谢您发现此问题。实际上忙碌的日子还在继续,所以如果您找到任何解决方案的贡献,我们将不胜感激。也请分享标签的“文本”。
      【解决方案4】:

      以适当的发布回答!!!!

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

      【讨论】:

        【解决方案5】:

        斯威夫特 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
        }
        

        【讨论】:

        • 你应该替换下面的代码 let text:NSString = label.text! // TODO: Make safe? with guard let text: NSString = self.text else { return [] }
        • 起初它对我不起作用 - 计算的行数比实际数字多。但后来我将 CGPathAddRect 的行更改为: CGPathAddRect(path, nil, CGRectMake(0, 0, rect.size.width+15, 100000)) 并且它按预期工作。
        • 这是不可靠的。见:stackoverflow.com/questions/46923039/…
        【解决方案6】:

        Swift 3 – Xcode 8.1

        我已将之前答案中的代码组合在一起,以创建与 Swift 3、Xcode 8.1 兼容的 extensionUILabel,返回标签的第一行。

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

        要使用它,只需像这样在您的 UILabel 实例上调用 firstLineString

        let firstLine = myLabel.firstLineString
        

        【讨论】:

          【解决方案7】:

          这是用于获取标签中所有行的 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
             }
          }
          

          【讨论】:

            【解决方案8】:

            关于 iOS 11+ 的非常重要的变化

            从 iOS 11 开始,Apple 有意更改了 UILabel自动换行 功能的行为,这会影响检测多行 UILabel 中各行的 String 内容。根据设计,UILabel 的自动换行现在可以避免孤立文本(新行中的单个单词),如下所述:word wrapping in iOS 11

            因此,如果避免孤立文本的新自动换行在特定行中生效,CTFrameGetLines(frame) 返回标签中所有行的CTLine 数组的方式将不再正常工作。相反,它会导致String 的部分内容通过新的自动换行设计属于下一行,而不是在焦点所在的行中结束。

            可以在我的 @TheTiger's answer 更改版本中找到针对此问题的经过测试的修复程序,它使用 sizeThatFits(size:) 计算 UILabel 的实际内容大小,然后使用该大小创建 rect / 路径用 Swift 4 编写:

            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 数组返回,每行一个条目与呈现给用户的外观完全相同。

            【讨论】:

            • 注意:您需要在path.addRect 调用中将ceil(_:) 用于height,以避免丢失某些字体的最后一行。
            【解决方案9】:

            接受的答案非常好。

            我重构了两个地方:

            1. 10000更改为CGFloat.greatestFiniteMagnitude

            2. 将其添加到 extensionUILabel

            3. 我还想提一下,如果您通过设置框架来创建标签,它可以正常工作。如果您使用自动布局,请不要忘记调用

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

            【讨论】:

              【解决方案10】:

              抱歉,我的声誉太低,无法发表评论。 这是 Philipp Jahoda 对 https://stackoverflow.com/a/53783203/2439941 的评论。

              在我们在 UILabel 上启用动态类型之前,您的代码 sn-p 运行良好。当我们在 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
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-02-19
                • 2023-03-13
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多