【问题标题】:Determine last line's width确定最后一行的宽度
【发布时间】:2012-04-17 14:21:44
【问题描述】:

我有一个多行标签,lineBreakMode 设置为 UILineBreakModeWordWrap。如何确定最后一行的宽度?

【问题讨论】:

  • 我不知道有什么方法可以做到这一点。但是你为什么要这个?也许如果我们知道您为什么要这样做,我们可以想出另一种方法来解决您的问题。
  • 我可以想象一些复杂的程序,你重复使用 NSString 的sizeWithFont:constrainedToSize:lineBreakMode:,一次添加一个单词,找出哪个单词将你推到下一行,然后重复这个过程直到你到达最后一行,然后是最后的 sizeWithFont:constrainedToSize:lineBreakMode: 来计算最后一行的宽度。
  • 我使用了这种方法,但它看起来很复杂,所以我想知道是否有一些很好的解决方案。无论如何,谢谢!
  • 嘿@leon4ic 你有没有找到更好的解决方案?

标签: ios uilabel


【解决方案1】:

从 iOS 7.0 开始,您可以使用此功能来执行此操作(也许您需要根据自己的情况对文本容器进行更多调整):

public func lastLineMaxX(message: NSAttributedString, labelWidth: CGFloat) -> CGFloat {
    // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
    let labelSize = CGSize(width: bubbleWidth, height: .infinity)
    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: labelSize)
    let textStorage = NSTextStorage(attributedString: message)

    // Configure layoutManager and textStorage
    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)

    // Configure textContainer
    textContainer.lineFragmentPadding = 0.0
    textContainer.lineBreakMode = .byWordWrapping
    textContainer.maximumNumberOfLines = 0

    let lastGlyphIndex = layoutManager.glyphIndexForCharacter(at: message.length - 1)
    let lastLineFragmentRect = layoutManager.lineFragmentUsedRect(forGlyphAt: lastGlyphIndex,
                                                                  effectiveRange: nil)

    return lastLineFragmentRect.maxX
}

目标-C:

- (CGFloat)lastLineMaxXWithMessage:(NSAttributedString *)message labelWidth:(CGFloat)labelWidth
{
    CGSize labelSize = CGSizeMake(labelWidth, CGFLOAT_MAX);
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:labelSize];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:message];
    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];

    textContainer.lineFragmentPadding = 0;
    textContainer.lineBreakMode = NSLineBreakByWordWrapping;
    textContainer.maximumNumberOfLines = 0;
    NSUInteger lastGlyphIndex = [layoutManager glyphIndexForCharacterAtIndex:[message length] - 1];
    CGRect lastLineFragmentRect = [layoutManager lineFragmentUsedRectForGlyphAtIndex:lastGlyphIndex effectiveRange:nil];
    return CGRectGetMaxX(lastLineFragmentRect);
}

然后您可以决定最后一行是否有足够的位置放置您的日期标签

用法示例:

// you definitely have to set at least the font to calculate the result
// maybe for your case you will also have to set other attributes
let attributedText = NSAttributedString(string: label.text, 
                                        attributes: [.font: label.font])
let lastLineMaxX = lastLineMaxX(message: attributedText, 
                                labelWidth: label.bounds.width)

【讨论】:

    【解决方案2】:

    这就是我的做法。首先将标签的行放在 NSArray 中,然后检查最后一行的宽度。 在 viewDidLoad 中:

    NSArray* lines = [self getSeparatedLinesFromLbl:srcLabel];
    NSString *lastLine=[lines lastObject];
    float lastLineWidth=[lastLine sizeWithFont:srcLabel.font constrainedToSize:boundingSize lineBreakMode:NSLineBreakByWordWrapping].width;
    

    和 getSeparatedLinesFromLbl:

    -(NSArray*)getSeparatedLinesFromLbl:(UILabel*)lbl
    {
    if ( lbl.lineBreakMode != NSLineBreakByWordWrapping )
    {
        return nil;
    }
    
    NSMutableArray* lines = [NSMutableArray arrayWithCapacity:10];
    
    NSCharacterSet* wordSeparators = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    
    NSString* currentLine = lbl.text;
    int textLength = [lbl.text length];
    
    NSRange rCurrentLine = NSMakeRange(0, textLength);
    NSRange rWhitespace = NSMakeRange(0,0);
    NSRange rRemainingText = NSMakeRange(0, textLength);
    BOOL done = NO;
    while ( !done )
    {
        // determine the next whitespace word separator position
        rWhitespace.location = rWhitespace.location + rWhitespace.length;
        rWhitespace.length = textLength - rWhitespace.location;
        rWhitespace = [lbl.text rangeOfCharacterFromSet: wordSeparators options: NSCaseInsensitiveSearch range: rWhitespace];
        if ( rWhitespace.location == NSNotFound )
        {
            rWhitespace.location = textLength;
            done = YES;
        }
    
        NSRange rTest = NSMakeRange(rRemainingText.location, rWhitespace.location-rRemainingText.location);
    
        NSString* textTest = [lbl.text substringWithRange: rTest];
    
        CGSize sizeTest = [textTest sizeWithFont: lbl.font forWidth: 1024.0 lineBreakMode: NSLineBreakByWordWrapping];
        if ( sizeTest.width > lbl.bounds.size.width )
        {
            [lines addObject: [currentLine stringByTrimmingCharactersInSet:wordSeparators]];
            rRemainingText.location = rCurrentLine.location + rCurrentLine.length;
            rRemainingText.length = textLength-rRemainingText.location;
            continue;
        }
    
        rCurrentLine = rTest;
        currentLine = textTest;
    }
    
    [lines addObject: [currentLine stringByTrimmingCharactersInSet:wordSeparators]];
    
    return lines;
    }
    

    【讨论】:

      【解决方案3】:

      更新 Swift 4.2 (IOS 12)

      extension UILabel {
      func getSeparatedLines() -> [Any] {
          if self.lineBreakMode != NSLineBreakMode.byWordWrapping {
              self.lineBreakMode = .byWordWrapping
          }
          var lines = [Any]() /* capacity: 10 */
          let wordSeparators = CharacterSet.whitespacesAndNewlines
          var currentLine: String? = self.text
          let textLength: Int = (self.text?.count ?? 0)
          var rCurrentLine = NSRange(location: 0, length: textLength)
          var rWhitespace = NSRange(location: 0, length: 0)
          var rRemainingText = NSRange(location: 0, length: textLength)
          var done: Bool = false
          while !done {
              // determine the next whitespace word separator position
              rWhitespace.location = rWhitespace.location + rWhitespace.length
              rWhitespace.length = textLength - rWhitespace.location
              rWhitespace = (self.text! as NSString).rangeOfCharacter(from: wordSeparators, options: .caseInsensitive, range: rWhitespace)
              if rWhitespace.location == NSNotFound {
                  rWhitespace.location = textLength
                  done = true
              }
              let rTest = NSRange(location: rRemainingText.location, length: rWhitespace.location - rRemainingText.location)
              let textTest: String = (self.text! as NSString).substring(with: rTest)
              let fontAttributes: [String: Any]? = [NSAttributedString.Key.font.rawValue: font]
              let maxWidth = (textTest as NSString).size(withAttributes: [NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue): font]).width
              if maxWidth > self.bounds.size.width {
                  lines.append(currentLine?.trimmingCharacters(in: wordSeparators) ?? "")
                  rRemainingText.location = rCurrentLine.location + rCurrentLine.length
                  rRemainingText.length = textLength - rRemainingText.location
                  continue
              }
              rCurrentLine = rTest
              currentLine = textTest
          }
          lines.append(currentLine?.trimmingCharacters(in: wordSeparators) ?? "")
          return lines
      }
      
      var lastLineWidth: CGFloat {
          let lines: [Any] = self.getSeparatedLines()
          if !lines.isEmpty {
              let lastLine: String = (lines.last as? String)!
              let fontAttributes = [NSAttributedString.Key.font.rawValue: font]
              return (lastLine as NSString).size(withAttributes: [NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue): font]).width
          }
          return 0
      }
      
      
      
      
      }
      

      用法

      print(yourLabel.lastLineWidth)
      

      Swift 4.2 (IOS 12)

      【讨论】:

        【解决方案4】:

        我发现 NSLayoutManager 已经为此提供了 API:

        // Returns the usage rect for the line fragment in which the given glyph is laid and (optionally) by reference the whole range of glyphs that are in that fragment.  
        // This will cause glyph generation and layout for the line fragment containing the specified glyph, or if non-contiguous layout is not enabled, up to and including that line fragment.  
        // Line fragment used rects are always in container coordinates.
        open func lineFragmentUsedRect(forGlyphAt glyphIndex: Int, effectiveRange effectiveGlyphRange: NSRangePointer?) -> CGRect
        

        所以,我认为我们可以这样做:

        let lastGlyphIndex = self.text.count - 1
        let lastLineWidth = layoutManager.lineFragmentUsedRect(forGlyphAt: lastGlyphIndex, effectiveRange: nil).size.with
        

        【讨论】:

          猜你喜欢
          • 2019-04-21
          • 1970-01-01
          • 2012-06-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多