Andrew Chang
Andrew Chang

Reputation: 1319

Can I know the exact rect of part of NSTextField text?

for example, I have an NSTextField displayed as a label, whose content is: "Hello, Apple! And here I come!".

I want to locate the "Apple" section, and do some operation on exactly the characters above them (for example, check if -mouseDown: event is triggered on them). Can I get the position and size of this word?

Thank you in advance!


According to alastair's answer, I wrote an .m file to test that. Here is the digest:

/* Some definations to identify the complete text and the link */
#define _CFG_AMCHyperLinkTest_Text  @"Hello, this is an Apple website!"
#define _CFG_AMCHyperLinkTest_Link  @"Apple"

And the actual testing code:

/* test */
- (void)oneTimeInit
{
    AMCDebug(@"Pre Text rect: %@\nlink rect: %@",
         [AMCTools descriptionWithNSRect:[_labelHyperlink frame]],
         [AMCTools descriptionWithNSRect:[_labelLink frame]]);

    NSRange linkRange = [_CFG_AMCHyperLinkTest_Text rangeOfString:_CFG_AMCHyperLinkTest_Link];
    NSRect boundingRect;

    [_labelHyperlink setStringValue:_CFG_AMCHyperLinkTest_Text];

    _layoutManager = [[NSLayoutManager alloc] init];
    _textStorage = [[NSTextStorage alloc] initWithString:@"Apple"];
    _textContainer = [[NSTextContainer alloc] initWithContainerSize:[_labelHyperlink frame]];

    [_layoutManager setTextStorage:_textStorage];
    [_layoutManager addTextContainer:_textContainer];
    linkRange = [_layoutManager glyphRangeForCharacterRange:linkRange
                                   actualCharacterRange:NULL];

    AMCDebug(@"Char range: %@\nActual range: %@",
         [AMCTools descriptionWithNSRange:[_CFG_AMCHyperLinkTest_Text rangeOfString:_CFG_AMCHyperLinkTest_Link]],
         [AMCTools descriptionWithNSRange:linkRange]);

    boundingRect = [_layoutManager boundingRectForGlyphRange:linkRange inTextContainer:_textContainer];

    AMCDebug(@"Bounding rect: %@", [AMCTools descriptionWithNSRect:boundingRect]);

    /* Now, we should set the link rect */
    NSRect linkRect;

    linkRect.size = [AMCTools string:_CFG_AMCHyperLinkTest_Link sizeWithSystemFontSize:13.0F];
    linkRect.origin.x = [_labelHyperlink frame].origin.x + boundingRect.origin.x;
    linkRect.origin.y = [_labelHyperlink frame].origin.y + boundingRect.origin.y;

    // I have another text field who used to catch some mouse event and acted as an hyperlink. 
    // There are some codes to apply the "linkRect" to it. 
    ...... /* Some codes less important */

    AMCDebug(@"Text rect: %@\nlink rect: %@",
         [AMCTools descriptionWithNSRect:[_labelHyperlink frame]],
         [AMCTools descriptionWithNSRect:linkRect]);
}

And there are some description for some custom methods appeared above:

AMCTools: My own tool to print some debug info of some instance or data structures -string:sizeWithSystemFontSize:: A method to determine minimum size of an text in system font. AMCDebug(): Like "NSLog" and don't mind.

And here is the output:

>> -[AMCHyperLinkTestViewController oneTimeInit]
 2014-01-23 11:26:36.974 AMCTest[6665:303] 
Pre Text rect: (17.0,443.0), 199.0*17.0
link rect: (64.0,47.0), 92.0*17.0

>> -[AMCHyperLinkTestViewController oneTimeInit]
 2014-01-23 11:26:36.979 AMCTest[6665:303] 
String size: 194.3*17.0

>> Line 74, -[AMCHyperLinkTestViewController oneTimeInit]
 2014-01-23 11:26:36.995 AMCTest[6665:303] 
Char range: (18 -> 23 <5>)
Actual range: (5 -> 5 <0>)

>> -[AMCHyperLinkTestViewController oneTimeInit]
 2014-01-23 11:26:37.003 AMCTest[6665:303] 
Bounding rect: (37.0,0.0), 0.0*14.0                 <-- This does not look correct

>> -[AMCHyperLinkTestViewController oneTimeInit]
 2014-01-23 11:26:37.007 AMCTest[6665:303] 
Text rect: (17.0,443.0), 199.0*17.0
link rect: (54.0,443.0), 36.3*17.0

It did not work as I expected.

Upvotes: 2

Views: 1391

Answers (1)

al45tair
al45tair

Reputation: 4433

If you’re simply interested in putting in a hyperlink, you can simply set the attributed string value to an attributed string containing a suitable hyperlink. (To do this, set an attribute of type NSLinkAttributeName on the range you want to make into a link, with the value set to an NSURL object. You may also wish to set an NSCursorAttributeName attribute to change the user’s mouse pointer, and maybe other attributes as well to highlight the link to the user.)

If you’re trying to do something a bit more complicated, first note that NSTextField will not render itself when editing (since it is actually the field editor that appears when it’s being edited), so doing this kind of thing for editable NSTextFields is more complicated.

Otherwise, you can use NSLayoutManager to calculate the layout for you, which will tell you where the word in question is located and what size it is. This is quite complicated (because text rendering is quite complicated), but essentially you need to:

  1. Create an NSLayoutManager.
  2. Create an NSTextStorage and put the text from the field into it.
  3. Create an NSTextContainer of the appropriate size; for a single-line field, you should make this very wide to avoid wrapping, but for a multi-line field you want to make this the same size as the field (less any borders).
  4. Attach the text storage and the container to your layout manager (-setTextStorage:, -addTextContainer:).
  5. Convert the text range for your substring into a glyph range using -glyphRangeForCharacterRange:actualCharacterRange:.
  6. Get the bounding rect using e.g. -boundingRectForGlyphRange:inTextContainer:.

Don't do all of this just to make a hyperlink, though. Just use the built-in support for that.

Upvotes: 6

Related Questions