I have an NSMutableAttributedString such as \"Bob liked your picture\".
I\'m wondering if I can add two different tap events to \"Bob\" and \"picture\". Ideally, tap
You can achieve this by using CoreText to implement a method that will retrieve the index of the character the user selected / touched. First, using CoreText, draw your attributed string in a custom UIView
sub class. An example overridden drawRect:
method:
- (void) drawRect:(CGRect)rect
{
// Flip the coordinate system as CoreText's origin starts in the lower left corner
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0.0f, self.bounds.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(_attributedString));
if(textFrame != nil) {
CFRelease(textFrame);
}
// Keep the text frame around.
textFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path.CGPath, NULL);
CFRetain(textFrame);
CTFrameDraw(textFrame, context);
}
Secondly, create a method that interrogates the text to find the character index for a given point:
- (int) indexAtPoint:(CGPoint)point
{
// Flip the point because the coordinate system is flipped.
point = CGPointMake(point.x, CGRectGetMaxY(self.bounds) - point.y);
NSArray *lines = (__bridge NSArray *) (CTFrameGetLines(textFrame));
CGPoint origins[lines.count];
CTFrameGetLineOrigins(textFrame, CFRangeMake(0, lines.count), origins);
for(int i = 0; i < lines.count; i++) {
if(point.y > origins[i].y) {
CTLineRef line = (__bridge CTLineRef)([lines objectAtIndex:i]);
return CTLineGetStringIndexForPosition(line, point);
}
}
return 0;
}
Lastly, you can override the touchesBegan:withEvent:
method to get the location of where the user touched and convert that into a character index or range:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *t = [touches anyObject];
CGPoint tp = [t locationInView:self];
int index = [self indexAtPoint:tp];
NSLog(@"Character touched : %d", index);
}
Be sure to include CoreText into your project and clean up any resources (like text frames) you keep around as that memory is not managed by ARC.
The way I would handle it is using a standard NSString
in a UITextView
. Then taking advantage of the UITextInput
protocol method firstRectForRange:
. Then you could easily overlay an invisible UIButton
in that rect and handle the action you'd like to take.