I am using CoreText to layout a custom view. The next step for me is to detect which word is tapped on a touch event/gesture event. I have done research on this and found advise on how to custom label a url to receive touches – but nothing generic. Does any one have any idea on how to do this?
UPDATE:
Here is the code within my drawRect: method
self.attribString = [aString copy];
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable(); //1
CGPathAddRect(path, NULL, self.bounds );
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)aString); //3
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [aString length]), path, NULL);
CTFrameDraw(frame, context); //4
UIGraphicsPushContext(context);
frameRef = frame;
CFRelease(path);
CFRelease(framesetter);
Here is where I am trying to handle the touch event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
CGContextRef context = UIGraphicsGetCurrentContext();
CFArrayRef lines = CTFrameGetLines(frameRef);
for(CFIndex i = 0; i < CFArrayGetCount(lines); i++)
{
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGRect lineBounds = CTLineGetImageBounds(line, context);
NSLog(@"Line %ld (%f, %f, %f, %f)", i, lineBounds.origin.x, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height);
NSLog(@"Point (%f, %f)", point.x, point.y);
if(CGRectContainsPoint(lineBounds, point))
{
It seems that CTLineGetImageBounds is returning a wrong origin (the size seems correct) here is one example of the NSLog “Line 0 (0.562500, -0.281250, 279.837891, 17.753906)”.
There is no “current context” in
touchesEnded:withEvent:. You are not drawing at this point. So you can’t callCTLineGetImageBounds()meaningfully.I believe the best solution here is to use
CTFrameGetLineOrigins()to find the correct line (by checking the Y origins), and then usingCTLineGetStringIndexForPosition()to find the correct character within the line (after subtracting the line origin frompoint). This works best for simple stacked lines that run the entire view (such as yours).Other solutions:
Calculate all the line rectangles during
drawRect:and cache them. Then you can just do rectangle checks intouchesEnded:.... That’s a very good solution if drawing is less common than tapping. If drawing is significantly more common than tapping, then that’s a bad approach.Do all your calculations using
CTLineGetTypographicBounds(). That doesn’t require a graphics context. You can use this to work out the rectangles.In
drawRect:generate a CGLayer with the current context and store it in an ivar. Then use the context from theCGLayerto calculateCTLineGetImageBounds(). The context from theCGLayerwill be “compatible” with the graphics context you’re using to draw.Side note: Why are you calling
UIGraphicsPushContext(context);indrawRect:? You’re setting the current context to the current context. That doesn’t make sense. And I don’t see a correspondingUIGraphicsPopContext().