CoreText mapping characters
Asked Answered
M

2

9

I have some in a touch handler which responds to a tap on a view that I've drawn some attributed text in. through this, I've got to the point where I have a CTRunRef (and the associated line) as well as the number of glyphs in that run.

What I'm not able to figure out easily, is how I can take that run of glyphs and, given my attributed string, map it out to characters in the string.

Specifically the problem is I would like to know what word the user tapped on in the view, so I can process whether or not that word is a URL and fire off a custom delegate method so I can open a web view with it. I have all the possible substrings, I just don't know how to map where the user tapped to a particular substring.

Any help would be greatly appreciated.

UPDATE: I've actually gone and done it a different way, on the suggestion of another person off of stackoverflow. Basically what I've done is to set a custom attribute, @"MyAppLinkAddress" with the value of the URL I found when I was converting the string to an attributed string. This happens before I draw the string. Therefore, when a tap event occurs, I just check if that attribute exists, and if so, call my delegate method, if not, just ignore it. It is working how I'd like now, but I'm going to leave this question open for a few more days, if someone can come up with an answer, I'll happily accept it if its a working solution so that some others may be able to find this information useful at some point in the future.

Marie answered 29/9, 2010 at 14:28 Comment(0)
M
7

So as I mentioned in the update, I elected to go a different route. Instead I got the idea to use a custom attribute in the attributed string to specify my link, since I had it at creation time anyway. So I did that. Then in my touch handler, when a run is tapped, I check if that run has that attribute, and if so, call my delegate with it. From there I'm happily loading a webview with that URL.

EDIT: Below are snippets of code explaining what I did in this answer. Enjoy.

// When creating the attribute on your text store. Assumes you have the URL already. 
// Filled in for convenience
NSRange urlRange = [tmpString rangeOfString:@"http://www.foo.com/"];
[self.textStore addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[UIColor blueColor].CGColor range:urlRange];
[self.textStore addAttribute:@"CustomLinkAddress" value:urlString range:urlRange];

then...

// Touch handling code — Uses gesture recognizers, not old school touch handling.
// This is just a dump of code actually in use, read through it, ask questions if you
// don't understand it. I'll do my best to put it in context.
- (void)receivedTap:(UITapGestureRecognizer*)tapRecognizer
{
        CGPoint point = [tapRecognizer locationInView:self];

        if(CGRectContainsPoint(textRect, point))
        {
                CGContextRef context = UIGraphicsGetCurrentContext();

                point.y = CGRectGetHeight(self.contentView.bounds) - kCellNameLabelHeight - point.y;

                CFArrayRef lines = CTFrameGetLines(ctframe);
                CFIndex lineCount = CFArrayGetCount(lines);
                CGPoint origins[lineCount];
                CTFrameGetLineOrigins(ctframe, CFRangeMake(0, 0), origins);
                for(CFIndex idx = 0; idx < lineCount; idx++)
                {
                        CTLineRef line = CFArrayGetValueAtIndex(lines, idx);
                        CGRect lineBounds = CTLineGetImageBounds(line, context);
                        lineBounds.origin.y += origins[idx].y;

                        if(CGRectContainsPoint(lineBounds, point))
                        {
                                CFArrayRef runs = CTLineGetGlyphRuns(line);
                                for(CFIndex j = 0; j < CFArrayGetCount(runs); j++)
                                {
                                        CTRunRef run = CFArrayGetValueAtIndex(runs, j);
                                        NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
                                        NSString* urlString = [attributes objectForKey:@"CustomLinkAddress"];
                                        if(urlString && ![urlString isEqualToString:@""])
                                        {
                                                [self.delegate didReceiveURL:[NSURL URLWithString:urlString]];
                                                UIGraphicsPopContext();
                                                return;
                                        }
                                }
                        }
                }
                UIGraphicsPopContext();
        }
}
Marie answered 1/10, 2010 at 6:41 Comment(1)
You don't need to call UIGraphicsPopContext() here. You're just getting the context, not pushing a new one.Equiponderate
E
3

After you find the tapped line, you can ask for the index in string by calling CTLineGetStringIndexForPosition(). There's no need to access individual runs.

Equiponderate answered 17/5, 2012 at 21:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.