【问题标题】:Counting the number of lines in a UITextView, lines wrapped by frame size计算 UITextView 中的行数,按帧大小包裹的行数
【发布时间】:2023-04-05 18:22:02
【问题描述】:

我想知道文本何时被文本视图的框架包裹,是否有任何分隔符可以用来识别文本是否被包裹。

例如,如果我的文本视图的宽度为 50 像素并且文本超过了该宽度,它会将文本换行到下一行。

我想计算文本视图中的行数。现在 "\n" 和 "\r" 对我没有帮助。

我的代码是:

NSCharacterSet *aCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"\n\r"];
    NSArray *myArray = [textViewText componentsSeparatedByCharactersInSet:aCharacterSet];
    NSLog(@"%d",[myArray count]);

【问题讨论】:

标签: ios objective-c cocoa-touch uitextview


【解决方案1】:

您需要使用lineHeight 属性和字体lineHeight

Objective-C

int numLines = txtview.contentSize.height / txtview.font.lineHeight;

斯威夫特

let numLines = (txtview.contentSize.height / txtview.font.lineHeight) as? Int

我得到了正确的行数。

【讨论】:

【解决方案2】:

这种变化考虑了你如何换行和UITextView 的最大尺寸,并且可能会输出更精确的高度。例如,如果文本不合适,它将被截断为可见大小,并且如果您将整个单词换行(这是默认设置),则可能会导致比您不这样做的行数更多。

UIFont *font = [UIFont boldSystemFontOfSize:11.0];
CGSize size = [string sizeWithFont:font 
                      constrainedToSize:myUITextView.frame.size 
                      lineBreakMode:UILineBreakModeWordWrap]; // default mode
float numberOfLines = size.height / font.lineHeight;

【讨论】:

    【解决方案3】:

    Swift 扩展:

    使用@himanshu padia 回答

    //MARK: - UITextView
    extension UITextView{
    
        func numberOfLines() -> Int{
            if let fontUnwrapped = self.font{
                return Int(self.contentSize.height / fontUnwrapped.lineHeight)
            }
            return 0
        }
    
    }
    

    用法:yourTextView.numberOfLines()

    请注意,如果由于某种原因文本视图的字体为零,则返回为零。

    【讨论】:

      【解决方案4】:

      斯威夫特 3:

      let layoutManager:NSLayoutManager = textView.layoutManager
      let numberOfGlyphs = layoutManager.numberOfGlyphs
      var numberOfLines = 0
      var index = 0
      var lineRange:NSRange = NSRange()
      
      while (index < numberOfGlyphs) {
          layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
          index = NSMaxRange(lineRange);
          numberOfLines = numberOfLines + 1
      }
      
      print(numberOfLines)
      

      【讨论】:

      • 方法 numberOfLines() 不计算新行然后它有第一个 \n 符号。此方法不正确
      • @castillejoale 这没有在 iPad 上提供正确的行数。我们需要处理什么吗?
      【解决方案5】:

      Swift 4 版本的 Luke Chase 的 answer

      let numberOfGlyphs = textView.layoutManager.numberOfGlyphs
      var index = 0, numberOfLines = 0
      var lineRange = NSRange(location: NSNotFound, length: 0)
      
      while index < numberOfGlyphs {
        textView.layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
        index = NSMaxRange(lineRange)
        numberOfLines += 1
      }
      

      【讨论】:

      • 也适用于 Swift 5。
      【解决方案6】:

      我在 Apple 的 Text Layout Programming Guide 中找到了解决此问题的完美方法。以下是 Apple 提供的解决方案:

      NSLayoutManager *layoutManager = [textView layoutManager];
      unsigned numberOfLines, index;
      unsigned numberOfGlyphs = [layoutManager numberOfGlyphs];
      NSRange lineRange;
      
      for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
          (void) [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange];
          index = NSMaxRange(lineRange);
      }
      

      这可以很容易地写入 UITextView 的扩展,或者作为一个独立的方法,以 UITextView 对象作为参数

      【讨论】:

      • @florin-odagiu 方法 numberOfLines() 不计算新行然后它有第一个 \n 符号。此方法不正确
      【解决方案7】:

      斯威夫特 5.0

      extension UITextView {
          func sizeFit(width: CGFloat) -> CGSize {
              let fixedWidth = width
              let newSize = sizeThatFits(CGSize(width: fixedWidth, height: .greatestFiniteMagnitude))
              return CGSize(width: fixedWidth, height: newSize.height)
          }
      
          func numberOfLine() -> Int {
              let size = self.sizeFit(width: self.bounds.width)
              let numLines = Int(size.height / (self.font?.lineHeight ?? 1.0))
              return numLines
          }
      }
      

      【讨论】:

        【解决方案8】:

        你需要考虑textView.textContainerInset,还需要对计算的值进行四舍五入,因为行号肯定是整数

        float rawLineNumber = (textView.contentSize.height - textView.textContainerInset.top - textView.textContainerInset.bottom) / textView.font.lineHeight;
        int finalLineNumber = round(rawLineNumber)
        

        在实际情况下,您可能会看到以下结果 rawLineNumber = 3.008099 finalLineNumber = 3(3 行)

        【讨论】:

          【解决方案9】:

          使用这个(_text_v 是你的文本视图):

          -(NSInteger) linesCount {
              return _text_v.contentSize.height/_text_v.font.lineHeight;
          }
          

          【讨论】:

          • 嗨,马克斯。我尝试使用它,但是对于我的文本视图中的 8 行数据,此方法返回我的计数为 5。重要的是我在数据中有一些 7 \n 字符。
          【解决方案10】:

          我花了很多时间尝试实时计算输入 TextView(比如聊天文本输入框)的行数,在这种情况下唯一有效的解决方案是 Luke Chase 的(出于某种原因框架.height 方法似乎只更新输入到 textView 的第三或第四个字母,因此不准确)。

          然而,正如评论者所提到的,存在一个小错误,即用户生成的换行符(“\n”或键盘回车键)没有得到正确解释。更奇怪的是,它只会“错过”第一个这样的换行符,任何后续的换行符都会被正确捕获(假设你去该行 4 次,它只会返回 3 行显然错过了第一个换行符)。

          所以为了解决这个错误,我简单地记录下第一个这样的换行符(字符“\n”),然后手动将一行添加到 gliph 方法返回的行数中。

          在给出的代码中:

              func offSetTableViewIfNeeded() {
              let numberOfGlyphs = textView.layoutManager.numberOfGlyphs
              var index : Int = 0
              var lineRange = NSRange(location: NSNotFound, length: 0)
              var currentNumOfLines : Int = 0
              var numberOfParagraphJump : Int = 0
          
              while index < numberOfGlyphs {
                  textView.layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
                  index = NSMaxRange(lineRange)
                  currentNumOfLines += 1
          
                  // Observing whether user went to line and if it's the first such line break, accounting for it.
                  if textView.text.last == "\n", numberOfParagraphJump == 0 {
                      numberOfParagraphJump = 1
                  }
              }
          
              currentNumOfLines += numberOfParagraphJump
          
              print("Number of lines is:", currentNumOfLines)
          

          希望这可以帮助那些一直在为输入 textView 的超级怪异行为而苦苦挣扎的其他人(无法理解为什么 Apple 不提供开箱即用的 # of line 方法!)。

          【讨论】:

          • 感谢您的想法,这解决了我的问题。只是一个问题,如果最后一个字符是\n,为什么不在循环后添加一个新行?为什么要使用numberOfParagraphJump 执行此检查?
          【解决方案11】:

          改进并更新 Luke Chase 的 answer 到 Swift 5、XCode 11、iOS 13,以获取文本视图的行数和自动调整表格视图单元格的高度。

          1. 您可以使用带有静态单元格高度的情节提要来根据需要进行设计。使 UITextView scroll enable: false(禁用滚动)。

          2. 在 viewDidLoad 中添加您估计的行高和您的 textView 委托。

          override func viewDidLoad() {
                  super.viewDidLoad()
          
                  quoteTextView.delegate = self
                  tableView.estimatedRowHeight = 142
          
              }
          
          1. 为 heightForRowAt 添加表视图代理:
          override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
              return UITableView.automaticDimension
          }
          
          1. 符合 UITextViewDelegate 在用户输入文本时进行监听。
          extension ViewController: UITextViewDelegate {
              func textViewDidChange(_ textView: UITextView) {
                  // Refresh tableView cell
                  if textView.numberOfLines > 2 { // textView in storyboard has two lines, so we match the design
                      // Animated height update
                      DispatchQueue.main.async {
                          self.tableView?.beginUpdates()
                          self.tableView?.endUpdates()
                      }
                  }
              }
          }
          
          1. 添加 UITextView 扩展,这样您就可以避免冗余代码并在整个应用程序中使用。
          extension UITextView {
              var numberOfLines: Int {
                  // Get number of lines
                  let numberOfGlyphs = self.layoutManager.numberOfGlyphs
                  var index = 0, numberOfLines = 0
                  var lineRange = NSRange(location: NSNotFound, length: 0)
          
                  while index < numberOfGlyphs {
                      self.layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
                    index = NSMaxRange(lineRange)
                    numberOfLines += 1
                  }
          
                  return numberOfLines
              }
          }
          

          -> 不要忘记禁用 uitextview 滚动。干杯!

          预览

          【讨论】:

            【解决方案12】:
            extension UITextView {
                   func numberOfLines(textView: UITextView) -> Int {
                    let layoutManager = textView.layoutManager
                    let numberOfGlyphs = layoutManager.numberOfGlyphs
                    var lineRange: NSRange = NSMakeRange(0, 1)
                    var index = 0
                    var numberOfLines = 0
                    
                    while index < numberOfGlyphs {
                        layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
                        index = NSMaxRange(lineRange)
                        numberOfLines += 1
                    }
                    return numberOfLines
                }
            }
            

            对我来说工作得很好

            【讨论】:

            • 方法 numberOfLines() 不计算新行然后它有第一个 \n 符号。此方法不正确
            • @DmitryPetukhov 我知道已经有一段时间了,但添加了一个正确考虑换行符的解决方案here
            【解决方案13】:

            适用于那些拥有动态字体大小或混合或不同字体大小以进行丰富消息传递的用户。正如其他人正确指出的那样,您可以使用 NSLayoutManager 准确计算行高。

            正如 Dmitry Petukhov 所指出的那样,一个额外的问题是一个简单的实现,它没有考虑新行。您可以使用布局管理器通过检查 layoutManager.extraLineFragmentRect 按照文档 NSLayoutManager#extraLineFragmentRect 来解决这个问题

            在额外的线片段矩形中包围插入点的矩形

            矩形在其 NSTextContainer 的坐标系中定义。 NSZeroRect 如果没有额外的线片段矩形。

            所以我们只需要检测何时填充extraLineFragmentUsedRect (!= CGRect.zero) - 这意味着有一个换行符作为结尾,并相应地更正行数。

            下面的完整示例。

            extension UITextView {
                public var lineCount: Int {
                    let numberOfGlyphs = layoutManager.numberOfGlyphs
                    var index = 0, numberOfLines = 0
                    var lineRange = NSRange(location: NSNotFound, length: 0)
            
                    while index < numberOfGlyphs {
                        layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
                        index = NSMaxRange(lineRange)
                        numberOfLines += 1
                    }
                    // Take into account newlines at the bottom.
                    if layoutManager.extraLineFragmentUsedRect != CGRect.zero {
                        numberOfLines += 1
                    }
                    return numberOfLines
                }
            }
            

            【讨论】:

              【解决方案14】:

              我觉得你可以试试NSLayoutManager:

              NSLayoutManager *layoutManager = [textView layoutManager];
              unsigned numberOfLines, index, numberOfGlyphs =
                      [layoutManager numberOfGlyphs];
              NSRange lineRange;
              for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
                  (void) [layoutManager lineFragmentRectForGlyphAtIndex:index
                          effectiveRange:&lineRange];
                  index = NSMaxRange(lineRange);
              }
              

              参考

              CountLines

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-11-14
                • 2020-05-21
                • 2018-05-11
                • 2017-02-06
                • 1970-01-01
                • 2012-12-31
                相关资源
                最近更新 更多