NSPopover below caret in NSTextView

2019-03-22 06:21发布

问题:

I know that in order to show a popover I need an NSView, but I don't think that there is one associated with the caret (inside the NSTextView). Is there a way to show a NSPopover below the caret?

I tried to alloc a NSView and position it using (NSRect)boundingRectForGlyphRange:(NSRange)glyphRange inTextContainer:(NSTextContainer *)container, but the popover will not appear (and there's a reason, that method returns NSRect: {{0, 0}, {0, 0}}).

回答1:

I'm not sure if you are still looking for answer. I recently was working on a project which happens to need a very similar feature like you described.

You can do the following inside a subclass of NSTextView:

the function you are going to call is : showRelativeToRect:ofView:preferredEdge:

the rect will be a rect inside the NSTextView, using the NSTextView coordinate system, the ofView is the NSTextView, and the preferredEdge is the edge you want this popover thing to hook with.

now, you are saying that you want the PopOver thing to show under the caret, well you have to give him a Rect, a Point is not enough. The NSTextView has a selector called selectedRange, which gives you the range of the selected text, you can use that to locate your caret.

the next thing is to call firstRectForCharacterRange (the class must delegate NSTextInputClient), this method will return a screen coordinate of the selected text inside the NSTextView, then you convert them into the NSTextView coordinate system, you will be able to show the NSPopover thing at a correct position. Here's my code of doing this.

NSRect rect = [self firstRectForCharacterRange:[self selectedRange]]; //screen coordinates

// Convert the NSAdvancedTextView bounds rect to screen coordinates
NSRect textViewBounds = [self convertRectToBase:[self bounds]];
textViewBounds.origin = [[self window] convertBaseToScreen:textViewBounds.origin];

rect.origin.x -= textViewBounds.origin.x;
rect.origin.y -= textViewBounds.origin.y;    
rect.origin.y = textViewBounds.size.height - rect.origin.y - 10; //this 10 is tricky, if without, my control shows a little below the text, which makes it ugly.

NSLog(@"rect %@", NSStringFromRect(rect));
NSLog(@"bounds %@", NSStringFromRect([self bounds]));
if([popover isShown] == false)
    [popover showRelativeToRect:rect
                         ofView:self preferredEdge:NSMaxYEdge];

and this is the result.

All the thing I am wondering is that if there is a way to convert using System functions, although I tried the convertRect:toView, but since this method is written in a delegate, the NSTextView always has the coordinate system of (0,0), which makes this method useless.