Cocoa: how to show a popover under selected text
Showing a popover underneath a piece of selected text is a trick widely made use of across OSX, not least in Xcode itself, but also in many other apps. In this post we’re going to look at a simple way to achieve this effect.
1. First of all, in IB drag out a Popover and View Controller and drop them in the Document Outline area. Drag a custom view to the canvas, and hook them all up thus:
delegate –> App Delegate (or class that will control the Popover)
Popover View Controller’s Outlets
view –> Custom View
Click the Popover object in the Outline area, and in the Attributes Inspector, set the Behaviour to Transient.
In the appDelegate.h file, or the .h file of the class that will control your popover, include the PopoverDelegate in the @interface declaration:
@interface AppDelegate : NSObject <NSApplicationDelegate, NSPopoverDelegate>
2. Still in the header file, you need to make sure the window that the NSTextView is in has a View outlet in its header file.
@property (weak) IBOutlet NSView *aView;
For my purposes, I just have a single window in the appDelegate class, so I just created a view property by control-dragging out of the Window’s view object to the header file. You’ll need to switch to the dual-view Assistant editor to do this:
While you’ve got the Assistant editor open, drag out an outlet from the Popover to the .h file and name it ‘popover’. Finally, in the same way create a similar outlet for your TextView.
4. Next, go into the implementation .m file for the appDelegate (or you class). You’ll need an IBAction to trigger the showing of the popover. In my case, I have an ‘Enter’ button the user hits after making a selection attached to a method I called enterSelection:(NSButton *)sender.
In this method, I first get the rect for the user’s selection with:
NSRect rect = [_textView firstRectForCharacterRange:[_textView selectedRange] actualRange:NULL];
That will return a rect in screen coordinates for the selected text. However, I need to convert that into the window’s coordinates with:
NSRect converted = [_window convertRectFromScreen:rect];
Now we’re ready to call the popover by supplying this rect to the first parameter and the View property we created earlier to the second parameter:
[self.popover showRelativeToRect:converted ofView: _aView preferredEdge:NSMinYEdge];
And that’s it. Your popover should show underneath the selected text whenever your method gets called. If you want to see how this is done step by step in Xcode, check out the video: