I have a text view, where I want to detect links, but when there isn't a link at the touch point, it should propagate the touch to view below (it currently doesn't). It will be contained in a table view cell, and if the user taps a link, it should interact (it works), but when another point is tapped, it should select the table view cell.
I needed text to be unselectable, so I've followed https://stackoverflow.com/a/27264999/811405 and implemented:
-(BOOL)canBecomeFirstResponder{
return NO;
}
It wasn't sending the touch events below before that too, but I've included it just is case it interferes with a solution.
Instead of preventing your text view from becoming first responder, you need to subclass the hitTest
method so that it returns the textView when the click occured inside a link and return nil otherwise.
@interface LinkOnlyTextView : UITextView
@end
@implementation LinkOnlyTextView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSUInteger glyphIndex = [self.layoutManager glyphIndexForPoint:point inTextContainer:self.textContainer fractionOfDistanceThroughGlyph:nullptr];
NSUInteger characterIndex = [self.layoutManager characterIndexForGlyphAtIndex:glyphIndex];
if (characterIndex < self.textStorage.length) {
if ([self.textStorage attribute:NSLinkAttributeName atIndex:characterIndex effectiveRange:nullptr]) {
return self;
}
}
return nil;
}
@end
Here is a Swift version of @Dalzhim's answer, incorporating the adjustment from @jupham to check that point
is actually contained within glyphRect
.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let glyphIndex = self.layoutManager.glyphIndex(for: point, in: self.textContainer)
//Ensure the glyphIndex actually matches the point and isn't just the closest glyph to the point
let glyphRect = self.layoutManager.boundingRect(forGlyphRange: NSRange(location: glyphIndex, length: 1), in: self.textContainer)
if glyphIndex < self.textStorage.length,
glyphRect.contains(point),
self.textStorage.attribute(NSAttributedStringKey.link, at: glyphIndex, effectiveRange: nil) != nil {
return self
} else {
return nil
}
}