使用CALayer在跨越多行的UITextView中突出显示文本

9
这是 使用NSTextContainer和NSLayoutManager来用CALayer突出显示文本 的延续。我在获取每个行片段范围的正确矩形方面遇到了麻烦。请注意保留原有的HTML标签,不需要解释文本内容。
NSString* searchString = @"Returns the range of characters that generated";
NSRange match = [[[self textView]text]rangeOfString:searchString];
NSRange matchingGlyphRange = [manager glyphRangeForCharacterRange:match actualCharacterRange:NULL];



[manager enumerateLineFragmentsForGlyphRange:matchingGlyphRange usingBlock:
 ^(CGRect lineRect, CGRect usedRect, NSTextContainer *textContainer, NSRange lineRange, BOOL *stop) {

     NSRange currentRange = NSIntersectionRange(lineRange, matchingGlyphRange);

     [manager enumerateEnclosingRectsForGlyphRange:currentRange withinSelectedGlyphRange:NSMakeRange(NSNotFound, 0) inTextContainer:textContainer usingBlock:
      ^(CGRect rect, BOOL* stop) {
         if (usedRect.origin.y == rect.origin.y && NSLocationInRange(currentRange.location, lineRange)) {

             CGRect theRect = [manager boundingRectForGlyphRange:currentRange inTextContainer:textContainer];

             CALayer* roundRect = [CALayer layer];
             [roundRect setFrame:theRect];
             [roundRect setBounds:theRect];

             [roundRect setCornerRadius:5.0f];
             [roundRect setBackgroundColor:[[UIColor blueColor]CGColor]];
             [roundRect setOpacity:0.2f];
             [roundRect setBorderColor:[[UIColor blackColor]CGColor]];
             [roundRect setBorderWidth:3.0f];
             [roundRect setShadowColor:[[UIColor blackColor]CGColor]];
             [roundRect setShadowOffset:CGSizeMake(20.0f, 20.0f)];
             [roundRect setShadowOpacity:1.0f];
             [roundRect setShadowRadius:10.0f];

             [[[self textView]layer]addSublayer:roundRect];
             *stop = YES;
         }
     }];
}];

这是我的当前尝试。我基本上使用enumerateLineFragmentsForGlyphRange:usingBlock:循环遍历每一行。然后我使用enumerateEnclosingRectsForGlyphRange:withinSelectedGlyphRange:usingBlock:确保当前行文本范围与该行范围匹配,并具有相同的Y坐标。有时它有效,有时不起作用。
似乎如果搜索文本靠近回车键,它会将高亮显示偏移得很远(y坐标)。
在这个截图中,“返回生成字符的范围”应该被突出显示。
似乎如果文本不靠近回车或空格,这将正常工作:
如果周围没有空格,它会正确地工作。
我错过了什么吗?
更新:
我已经缩小了问题的范围,我相信enumerateLineFragmentsForGlyphRange:usingBlock:正在跳过具有返回值的行。
1个回答

9
我已经找到了解决方法:
不再使用enumerateEnclosingRectsForGlyphRange:,而是使用NSString的方法enumerateSubstringsInRange:options:usingBlock:
我按照通常的方式枚举行片段,但是不再尝试使用包围矩形枚举方法,而是在构建图层矩形时枚举每个字符。
-(void)drawLayerForTextHighlightWithString:(NSString*)string {

for (CALayer* eachLayer in [self highlightLayers]) {
    [eachLayer removeFromSuperlayer];
}

NSLayoutManager* manager = [[self textView]layoutManager];

// Find the string
NSRange match = [[[self textView]text]rangeOfString:string options:
                 NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch];

// Convert it to a glyph range
NSRange matchingGlyphRange = [manager glyphRangeForCharacterRange:match actualCharacterRange:NULL];

// Enumerate each line in that glyph range (this will fire for each line that the match spans)
[manager enumerateLineFragmentsForGlyphRange:matchingGlyphRange usingBlock:
 ^(CGRect lineRect, CGRect usedRect, NSTextContainer *textContainer, NSRange lineRange, BOOL *stop) {

     // currentRange uses NSIntersectionRange to return the range of the text that is on the current line
     NSRange currentRange = NSIntersectionRange(lineRange, matchingGlyphRange);

     // This rect will be built by enumerating each character in the line, and adding to it's width
     __block CGRect finalLineRect = CGRectZero;

     // Here we use enumerateSubstringsInRange:... to go through each glyph and build the final rect for the line
     [[[self textView]text]enumerateSubstringsInRange:currentRange options:NSStringEnumerationByComposedCharacterSequences usingBlock:
      ^(NSString* substring, NSRange substringRange, NSRange enclostingRange, BOOL* stop) {

          // The range of the single glyph being enumerated
          NSRange singleGlyphRange =  [manager glyphRangeForCharacterRange:substringRange actualCharacterRange:NULL];

          // get the rect for that glyph
          CGRect glyphRect = [manager boundingRectForGlyphRange:singleGlyphRange inTextContainer:textContainer];

          // check to see if this is the first iteration, if not add the width to the final rect for the line
          if (CGRectEqualToRect(finalLineRect, CGRectZero)) {
              finalLineRect = glyphRect;
          } else {
              finalLineRect.size.width += glyphRect.size.width;
          }

      }];

     // once we get the rect for the line, draw the layer
     UIEdgeInsets textContainerInset = [[self textView]textContainerInset];
     finalLineRect.origin.x += textContainerInset.left;
     finalLineRect.origin.y += textContainerInset.top;

     CALayer* roundRect = [CALayer layer];
     [roundRect setFrame:finalLineRect];
     [roundRect setBounds:finalLineRect];

     [roundRect setCornerRadius:5.0f];
     [roundRect setBackgroundColor:[[UIColor blueColor]CGColor]];
     [roundRect setOpacity:0.2f];
     [roundRect setBorderColor:[[UIColor blackColor]CGColor]];
     [roundRect setBorderWidth:3.0f];
     [roundRect setShadowColor:[[UIColor blackColor]CGColor]];
     [roundRect setShadowOffset:CGSizeMake(20.0f, 20.0f)];
     [roundRect setShadowOpacity:1.0f];
     [roundRect setShadowRadius:10.0f];

     [[[self textView]layer]addSublayer:roundRect];
     [[self highlightLayers]addObject:roundRect];

     // continues for each line
 }];

我仍在努力处理多个匹配项,一旦完成会更新代码。


嗨,自回答发布以来已经有一段时间了。你有这方面的更新代码吗? - fatuhoku
在iOS7下,高亮显示只会在用户编辑文本后才被绘制出来。第一轮高亮显示总是不正确的 - 看起来换行符被完全忽略了。然而,当下次更新文本时,它就完全正常了。 - fatuhoku
你好,这段代码有什么改进吗?设备旋转时如何修复高亮位置?有什么提示吗? - neowinston
有一段时间没有涉及这个了,但我猜想在设备旋转后需要重新计算高亮位置。我会研究布局管理器和/或文本容器何时重新计算布局和行位置。 - Myron Slaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接