I think I solved it, using a subclass of UITextView called which has a rangeOfLink property.
First, in my UIViewController viewDidLoad:, I add
self.textView.dataDetectorTypes = UIDataDetectorTypeLink; // change for other link types
self.textView.selectable = YES;
self.textView.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(handleTap:)];
tapGesture.cancelsTouchesInView = YES;
[self.textView addGestureRecognizer: tapGesture];
[self.textView setNeedsDisplay]; // force a redraw so that drawRect is called
Then in handleTap, I do this:
MyTextViewWithLink *aTextView = (IDTextViewWithLink *) recognizer.view;
if (aTextView != self.textView)
    return;
if (recognizer.state == UIGestureRecognizerStateEnded)
{
    CGPoint location = [recognizer locationInView: aTextView];
 // this returns an NSTextCheckingResult if location is inside a link
    NSTextCheckingResult *result = [self textCheckingResultAtPoint: location inTextView: aTextView]; 
    if (result)
    {
        aTextView.rangeOfLink = result.range;
        [aTextView setNeedsDisplay]; // this will force the color change
        // open url
    }
}
Finally I override drawRect in my UITextView subclass:
self.linkTextAttributes = [NSDictionary dictionary];
NSError *error = nil;
NSDataDetector *dataDetector = [NSDataDetector dataDetectorWithTypes: NSTextCheckingTypeLink error: &error];  // change for other link types
if (!error && dataDetector)
{
    NSArray* resultString = [dataDetector matchesInString: self.text
                                              options: NSMatchingReportProgress
                                                range: NSMakeRange(0, [self.text length])];
    if (resultString.count > 0)
    {
        NSMutableAttributedString *mas = [self.attributedText mutableCopy];
        for (NSTextCheckingResult* result in resultString)
        {
            if (result.resultType == NSTextCheckingTypeLink)
            {
                NSRange intersection = NSIntersectionRange(result.range, self.rangeOfLink);
                if (intersection.length <= 0) // no match
                    [mas addAttribute: NSForegroundColorAttributeName
                                value: [UIColor blueColor]
                                range: self.rangeOfLink];
                else
                    [mas addAttribute: NSForegroundColorAttributeName
                                value: [UIColor redColor]
                                range: self.rangeOfLink];
            }
        }
        self.attributedText = mas;
    }
}
[super drawRect: rect];
Now if the textView has more than one link, only the selected one will change color.