如何使用NSRulerView为NSTextView绘制行号?
问题:Objective-C实现NSTextView行号显示时NSRulerView子类drawHashMarksAndLabels方法未调用
我需要基于NSRulerView用Objective-C实现NSTextView的行号显示功能,尝试了NoodleKit、STTextView等多个开源项目均无法正常运行。将NSTextView-LineNumberView中可运行的Swift示例代码转换为Objective-C后,发现NSRulerView子类中的drawHashMarksAndLabels:方法始终未被调用,导致行号无法显示。
1. NSRulerView子类定义
@interface PERulerView: NSRulerView @end @implementation PERulerView - (id)initWithTextView:(NSTextView *)textView { NSScrollView *aScrollView = [textView enclosingScrollView]; self = [super initWithScrollView:aScrollView orientation: NSVerticalRuler]; if (self != nil) { self.clientView = textView; self.ruleThickness = 40; } return self; } - (void)drawHashMarksAndLabels:(NSRect)rect { // 从未被调用 NSLog(@"drawHashMarksAndLabels"); id obj = [self clientView]; if ([obj isKindOfClass:[NSTextView class]]){ [self doDrawHashMarksAndLabels: rect]; } } - (void)doDrawHashMarksAndLabels:(NSRect)rect { static BOOL isDrawing = NO; if(isDrawing) return; NSTextView *textView = (NSTextView *)[self clientView]; NSLayoutManager *layoutManager = [textView layoutManager]; if(!layoutManager) return; isDrawing = YES; NSLog(@"doDrawHashMarksAndLabels"); CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView]; NSMutableDictionary *attr = [NSMutableDictionary new]; attr[NSFontAttributeName] = textView.font; attr[NSForegroundColorAttributeName] = [NSColor redColor]; static void(^drawLineNumber)(NSDictionary *a, NSString *lineNumberString, CGFloat y) = nil; if (!drawLineNumber) { drawLineNumber = ^(NSDictionary *a, NSString *lineNumberString, CGFloat y) { NSAttributedString *attString = [[NSAttributedString alloc] initWithString:lineNumberString attributes:a]; NSSize stringSize = [lineNumberString sizeWithAttributes:a]; // 计算行号在侧边栏的X位置 CGFloat x = 40 - stringSize.width; // 绘制富文本 [attString drawAtPoint:NSMakePoint(x, y)]; NSLog(@"drawLineNumber %@.", lineNumberString); }; } NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer]; NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location]; NSRegularExpression *newLineRegex = [[NSRegularExpression alloc] initWithPattern:@"\\n" options:NSRegularExpressionCaseInsensitive error:nil]; NSRange matchRange = NSMakeRange(0, firstVisibleGlyphCharacterIndex); NSArray *matches = [newLineRegex matchesInString:textView.string options:NSMatchingReportProgress range:matchRange]; NSUInteger lineNumber = [matches count] + 1; NSLog(@"lineNumber: %ld", lineNumber); NSUInteger glyphIndexForStringLine = visibleGlyphRange.location; // 遍历可见区域内的每一行文本 while(glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) { // 获取当前文本行的字符范围 NSUInteger characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine]; NSRange characterRangeForStringLine = [textView.string lineRangeForRange:NSMakeRange(characterIndex, 0)]; NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange:characterRangeForStringLine actualCharacterRange:nil]; NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine; NSUInteger glyphLineCount = 0; // 处理文本换行后的多行 glyph while(glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) { NSRange effectiveRange = NSMakeRange(0, 0); // 获取当前glyph行的绘制区域 NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES]; if(glyphLineCount > 0) { drawLineNumber(attr, @"-", NSMinY(lineRect)+relativePoint.y); } else { drawLineNumber(attr, [NSString stringWithFormat:@"%ld", lineNumber], NSMinY(lineRect)+relativePoint.y); } glyphLineCount += 1; glyphIndexForGlyphLine = NSMaxRange(effectiveRange); } glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine); lineNumber += 1; } // 绘制文本末尾的额外行 if([layoutManager extraLineFragmentTextContainer] != nil) { drawLineNumber(attr, [NSString stringWithFormat:@"%ld", lineNumber], NSMinY(layoutManager.extraLineFragmentRect)+relativePoint.y); } isDrawing = NO; } @end
2. 视图控制器中初始化设置
@implementation ViewController @property (nonatomic, strong) IBOutlet PETextView *textView; - (void)viewDidLoad { [super viewDidLoad]; [self.textView initRulerView]; } @end
3. NSTextView子类中初始化RulerView
@interface PETextView: NSTextView { PERulerView *rulerView; } - (void)initRulerView; @end @implementation PETextView - (void)initRulerView { rulerView = [[PERulerView alloc] initWithTextView:self]; NSScrollView *scrollView = [self enclosingScrollView]; [scrollView setVerticalRulerView:rulerView]; [scrollView setHasHorizontalRuler:NO]; [scrollView setHasVerticalRuler:YES]; [scrollView setRulersVisible:YES]; self.postsFrameChangedNotifications = YES; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewTextDidChange:) name:NSTextDidChangeNotification object:self]; } - (void)textViewFrameDidChange:(NSNotification *)notif { [rulerView setNeedsDisplay:YES]; } - (void)textViewTextDidChange:(NSNotification *)notif { [rulerView setNeedsDisplay:YES]; } @end
运行环境
- Xcode 16.2
- macOS Sonoma 14.6.1
运行结果
运行后NSTextView左侧显示空白的行号侧边栏,无任何行号或标记绘制,控制台也未输出drawHashMarksAndLabels相关日志,证明该方法从未被调用。
内容的提问来源于stack exchange,提问作者Ashin
相关产品推荐
相关产品推荐

