Reputation: 2049
I have a textView that is layered on a tableView cell. I need the user to click on the tableView row to open a viewController but if the textView has a link it must be clickable and that's it, no copy, select etc.
If I allow user interaction like this:
textView.userInteractionEnabled=NO;
textView.dataDetectorTypes =UIDataDetectorTypeLink;
the didSelectRowAtIndexPath
is not called and I understand why but how to achieve this?
EDIT:
What I'm looking for is the ability to specifically identify link and allow user to click on the link to open a browser and disable interaction for the rest of the textView. Also the entire row should be clickable as said above.
Attached is the image of my tableView cell row which shows the link is detected and interaction is also possible but this disables the didSelectRowAtIndexPath
inside the textView region.
Upvotes: 11
Views: 6719
Reputation: 3187
Swift 5, iOS14
Adjusting @Saoud Rizwan Answer because hitTest
did not work well for me (it always called the same position multiple times). However here's my solution using UITapGestureRecognizer
, where a tap on a link inside a UITextView
will be detected.
First configure your Tap event to the UITextView
contentTextView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(textViewTapped(_:))))
The CellTapped
implementation:
@objc func cellTapped(_ sender: UITapGestureRecognizer) {
var location = sender.location(in: contentTextView)
location.x -= contentTextView.textContainerInset.left
location.y -= contentTextView.textContainerInset.top
let characterIndex = contentTextView.layoutManager.characterIndex(for: location, in: contentTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
if characterIndex < contentTextView.textStorage.length {
if let url = contentTextView.textStorage.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL {
// The user tapped on a link!
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return
}
}
}
// here: The user tapped somewhere in the textView but not on a link!
}
Upvotes: 0
Reputation: 659
If you are trying to add a UITextView with links to a cell, and want didSelectRow to be called when the user taps on anything but the link, then you should use hitTest:withEvent:
Swift 3
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// location of the tap
var location = point
location.x -= self.textContainerInset.left
location.y -= self.textContainerInset.top
// find the character that's been tapped
let characterIndex = self.layoutManager.characterIndex(for: location, in: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
if characterIndex < self.textStorage.length {
// if the character is a link, handle the tap as UITextView normally would
if (self.textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) != nil) {
return self
}
}
// otherwise return nil so the tap goes on to the next receiver
return nil
}
I wrote an article about this with a bit more details.
Upvotes: 8
Reputation: 1218
Maybe it will be ok for your purposes:
textView.editable=NO;
textView.userInteractionEnabled=YES;
textView.dataDetectorTypes =UIDataDetectorTypeLink;
But in fact UITextView uses UIWebView parts internally, and this way may be slowly in table cells.
I can advice to use CoreText
or NSAttributedString
's with touches. For example you may read this SO post.
Upvotes: 0
Reputation: 4271
One possible (and complex) solution would be using UIView
s with UITapGestureRecogniser
inside your UITextView
.
Firstly, you will need to find the NSRange
of your link.
Convert NSRange
to UITextRange
Use code similar to the following to add a UITapGestureRecogniser
right on top of the link-text in your UITextView
.
UITextPosition *pos = textView.endOfDocument;// textView ~ UITextView
for (int i = 0; i < words*2 - 1; i++){// *2 since UITextGranularityWord considers a whitespace to be a word
UITextPosition *pos2 = [textView.tokenizer positionFromPosition:pos toBoundary:UITextGranularityWord inDirection:UITextLayoutDirectionLeft];
UITextRange *range = [textView textRangeFromPosition:pos toPosition:pos2];
CGRect resultFrame = [textView firstRectForRange:(UITextRange *)range ];
UIView* tapViewOnText = [[UIView alloc] initWithFrame:resultFrame];
[tapViewOnText addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(targetRoutine)]];
tapViewOnText.tag = 125;
[textView addSubview:tapViewOnText];
[tapViewOnText release];
pos = pos2;
}
What I have done in this code is, got the UITextRange
of relevant text, get it's firstRectForRange
and added a transparent tap-able UIView
right on top of it.
You would have to get the range of your link using some regEx
, convert it to UITextRange
, and add tap-able UIViews
over them. In case, there might be more than one link in a single textView
you might add a tag
to each view corresponding to their 'link', and open that link in the target method by checking it's tag.
NOTE: If your UITextView
s are universally un-editable, you might want to try TTTAttributedLabel
instead. That is what I do in my UITableViewCells
Upvotes: 3