Add tap event to IOS NSMutableAttributedString

后端 未结 2 514
旧巷少年郎
旧巷少年郎 2021-01-07 03:38

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

2条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2021-01-07 04:04

    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.

提交回复
热议问题