When NSTextView is a subset of NSView that supports the layer ( -wantsLayer == YES ), it does not display reddish underlines for misspelled words. All that is required for playback is to create an empty Cocoa project, open nib, drag an NSTextView into the window, and switch the view of the contents of the window to want the layer. Boom - more red underscores.
I did some searches, and this seems to be a known situation, and this has been true since 10.5. However, I cannot find a workaround. Is there a way to get underscores if the NSTextView is in layer-supported mode?
I can imagine by overriding NSTextView drawRect: and using the layout manager to find the correct rectangles with the appropriate temporary attribute sets that indicate spelling errors, and then draw red squiggles, but this is of course a common hack. I can also imagine that Apple fixes this in 10.7 (maybe), and suddenly my application will have double underscores or something like that.
[update] My workaround
My current workaround was inspired by nptacek with the mentioned spell checking method, which prompted me to dig deeper along a path that I had not noticed before, so I am going to accept this answer, but will post what I did for posterity and / or further discussion.
I am running 10.6.5. I have a subclass of NSTextView, which is a document view for a custom subclass of NSClipView, which in turn is a subview of my contentView window, which has layers. Playing with this, I finally commented out all the settings, and the spellchecker worked incorrectly.
I have identified, in my opinion, two different problems:
# 1 is that an NSTextView, if placed in layer-supported mode, doesn't even bother to draw underscores with spelling errors. (I'm going based on Googleβs search queries that there may have been a time of 10.5 days when he was drawing underscores, but not in the right place - so Apple might just have disabled them completely to avoid this problem in 10.6 . I'm not sure. There may also be some side effect of how I position things, etc., which caused them to not appear at all in my case. Currently unknown.)
# 2 is that when an NSTextView is in this situation related to this layer, it seems that the text is not typed correctly with an error while entering it - even if the -isContinuousSpellCheckingEnabled parameter is set to YES. I checked this by following some delegation methods of spelling checking and watching how NSTextView sent change messages, but never notified of setting any text ranges as spelling - even with explicitly misspelled words that displayed a red underline in TextEdit (and other text types in other applications). I also tried NSTextView -handleTextCheckingResults: forRange: types: options: orthography: wordCount: see what it sees, and saw the same thing there. It was as if NSTextView was actively setting the word under the cursor as if it werenβt written with errors, and then when the user enters a space or moves away from it or something else, he does not reconsider for spelling errors. However, I'm not quite sure.
So, to get around # 1 , I overrode -drawRect: in my subclass of NSTextView to look like this:
- (void)drawRect:(NSRect)rect { [super drawRect:rect]; [self drawFakeSpellingUnderlinesInRect:rect]; }
Then I implemented -drawFakeSpellingUnderlinesInRect: to use layoutManager to get text ranges containing NSSpellingStateAttributeName as a temporary attribute and display a dot pattern close to standard OSX error patterns.
- (void)drawFakeSpellingUnderlinesInRect:(NSRect)rect { CGFloat lineDash[2] = {0.75, 3.25}; NSBezierPath *underlinePath = [NSBezierPath bezierPath]; [underlinePath setLineDash:lineDash count:2 phase:0]; [underlinePath setLineWidth:2]; [underlinePath setLineCapStyle:NSRoundLineCapStyle]; NSLayoutManager *layout = [self layoutManager]; NSRange checkRange = NSMakeRange(0,[[self string] length]); while (checkRange.length > 0) { NSRange effectiveRange = NSMakeRange(checkRange.location,0); id spellingValue = [layout temporaryAttribute:NSSpellingStateAttributeName atCharacterIndex:checkRange.location longestEffectiveRange:&effectiveRange inRange:checkRange]; if (spellingValue) { const NSInteger spellingFlag = [spellingValue intValue]; if ((spellingFlag & NSSpellingStateSpellingFlag) == NSSpellingStateSpellingFlag) { NSUInteger count = 0; const NSRectArray rects = [layout rectArrayForCharacterRange:effectiveRange withinSelectedCharacterRange:NSMakeRange(NSNotFound,0) inTextContainer:[self textContainer] rectCount:&count]; for (NSUInteger i=0; i<count; i++) { if (NSIntersectsRect(rects[i], rect)) { [underlinePath moveToPoint:NSMakePoint(rects[i].origin.x, rects[i].origin.y+rects[i].size.height-1.5)]; [underlinePath relativeLineToPoint:NSMakePoint(rects[i].size.width,0)]; } } } } checkRange.location = NSMaxRange(effectiveRange); checkRange.length = [[self string] length] - checkRange.location; } [[NSColor redColor] setStroke]; [underlinePath stroke]; }
So, after that I can see the red underscores, but it doesn't seem to update the spelling state on input. To work around this problem, I applied the following attackers in my subclass of NSTextView:
- (void)setNeedsFakeSpellCheck { if ([self isContinuousSpellCheckingEnabled]) { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(forcedSpellCheck) object:nil]; [self performSelector:@selector(forcedSpellCheck) withObject:nil afterDelay:0.5]; } } - (void)didChangeText { [super didChangeText]; [self setNeedsFakeSpellCheck]; } - (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag { [super updateInsertionPointStateAndRestartTimer:flag]; [self setNeedsFakeSpellCheck]; } - (void)forcedSpellCheck { [self checkTextInRange:NSMakeRange(0,[[self string] length]) types:[self enabledTextCheckingTypes] options:nil]; }
This does not work exactly the same as the real, expected OSX behavior, but it is sorted closely, and it is currently executing. I hope this helps someone else, or better yet, someone comes here and tells me that I am missing something incredibly simple and explaining how to fix it. :)