is there any chance to identify if empty space has been tapped in UITextView?

Viewed 131

I'm using UITextView in read-only mode + Gesture Recognizer to make it editable to support URLs, and it works pretty well but I've faced the issue: When a user has only the URL in the text and taps an empty space below it to make the UITextView editable - the URL got tapped instead and the user gets redirected to the URL.

Expected behavior: text should become editable.

The issue happens due to the following piece of code:

extension TextViewController : UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let textView = textView, textView.text.count > 0 {
            var location = touch.location(in: textView)
            location.x -= textView.textContainerInset.left
            location.y -= textView.textContainerInset.top
            let characterIndex = textView.layoutManager.characterIndex(for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
            if (textView.attributedText?.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL) != nil {
                return false
            }
        }
        return true
    }
}

Specifically due to "textView.layoutManager.characterIndex(for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)"

It returns the last digit accordingly to the documentation:

If no character is under the point, the nearest character is returned

So the code behaves as the URL has been tapped and I don't see any options to check if an empty space has been tapped. The idea is to check if there is no URL at the tapped location then gesture recognizer should receive a tap (return true here) Please suggest if you have any ideas how do that

Thank you!

1 Answers

Ok, so I got it working like expected. The solution is to check if the tapped position is the end of the document:

if let position = textView.closestPosition(to: location) {
    if position == textView.endOfDocument {
        return true
    }
}

The final code looks like that:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let textView = textView, textView.text.count > 0 {
            var location = touch.location(in: textView)
            location.x -= textView.textContainerInset.left
            location.y -= textView.textContainerInset.top
            if let position = textView.closestPosition(to: location) {
                if position == textView.endOfDocument {
                    return true
                }
            }
            
            let characterIndex = textView.layoutManager.characterIndex(for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
            if (textView.attributedText?.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL) != nil {
                return false
            }
        }
        return true
    }
Related