Detecting tap on a UITextView only detects cancellations

I did as Ralfonso said without success, but it helped me realize I had a gesture recognizer conflict. Turns out I didn't have to override all 4 methods of the UITextView, I just override the touchesBegan. What I was doing in the viewDidLoad was add a gesture recognizer to dismiss the keyboard :

      var tapOutTextField: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
      self.view.addGestureRecognizer(tapOutTextField)

What didn't make sense and sent me in a wrong direction was that touchesBegan was actually called after a delay (so was touchCancelled), and I could not get the same behaviour as a UIButton. All that because of this gesture recognizer conflict.


You're very close with your first attempt in subclassing UITextView, but instead of overriding only touchesCancelled, you'll want to override all of the touches* methods, i.e.

  • touchesBegan
  • touchesMoved
  • touchesEnded
  • touchesCancelled

In the overridden methods, send the touch down the responder chain by getting the textView's nextResponder(), check that it isn't nil, and call the method on the next responder.

The reason this works is because the UITextView will intercept the touch if it's on a URL and the UIResponder methods will never be called -- try it for yourself by implementing textView:shouldInteractWithURL:inRange in your tableViewCell or wherever, and you'll see that it's called before any touch events are passed along.

This should be the minimum for what you're trying to do (it's repetitive but short):

class PassThroughTextView: UITextView {

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let next = next {
            next.touchesBegan(touches, with: event)
        } else {
            super.touchesBegan(touches, with: event)
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let next = next {
            next.touchesEnded(touches, with: event)
        } else {
            super.touchesEnded(touches, with: event)
        }
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let next = next {
            next.touchesCancelled(touches, with: event)
        } else {
            super.touchesCancelled(touches, with: event)
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let next = next {
            next.touchesMoved(touches, with: event)
        } else {
            super.touchesMoved(touches, with: event)
        }
    }
}

// In case you want to do anything with the URL or textView in your tableViewCell...
class TableViewCell: UITableViewCell, UITextViewDelegate {
    @IBOutlet var textView: PassThroughTextView!

    func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
        println("textView shouldInteractWithURL")
        return true
    }

}