Add Uitapgesturerecognizer to Uitextview Without Blocking Textview Touches

Add UITapGestureRecognizer to UITextView without blocking textView touches

To do that make your view controller adopt to UIGestureRecognizerDelegate and override should recognize simultaneously with gesture recognizer method like:

override func viewDidLoad() {
tapTerm = UITapGestureRecognizer(target: self, action: "tapTextView:")
tapTerm.delegate = self
textView.addGestureRecognizer(tapTerm)
}

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {

return true
}

How To Add One UIGestureRecognizer to All UITextView without blocking textView touches

first u must add uitextfield delegate

<UITextFieldDelegate> 

then use that method

(BOOL)textFieldShouldBeginEditing:(UITextField *) textField

UITextView's UIGestureRecognizer preventing keyboard from appearing on tap

You could use the UIGestureRecognizerDelegate to make the UITapGestureRecognizer work along the regular UITextView behaviour:

class TestViewController: UIViewController {

@IBOutlet weak var textView: UITextView!

override func viewDidLoad() {
super.viewDidLoad()

let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap))
tapGestureRecognizer.delegate = self

textView.addGestureRecognizer(tapGestureRecognizer)
}

@objc private func tap() {
print("tap")
}

}

extension TestViewController: UIGestureRecognizerDelegate {

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}

}

UITextView with Gesture Recognizer - Conditionally Forward Touch to Parent View

After trying Swapnil Luktuke's answer(to the extent that I understood it, at least) to no avail, and every possible combination of:

  • Implementing the methods of UIGestureRecognizerDelegate,
  • Overriding UITapGestureRecognizer,
  • Conditionally calling ignore(_:for:), etc.

(perhaps in my desperation I missed something obvious, but who knows...)

...I gave up and decided to follow the suggestion by @danyapata in the comments to my question, and subclass UITextView.

Partly based on code found on this Medium post, I came up with this UITextView subclass:

import UIKit

/**
Detects taps on subregions of its attributed text that correspond to custom,
named attributes.

- note: If no tap is detected, the behavior is equivalent to a text view with
`isUserInteractionEnabled` set to `false` (i.e., touches "pass through"). The
same behavior doesn't seem to be easily implemented using just stock
`UITextView` and gesture recognizers (hence the need to subclass).
*/
class LinkTextView: UITextView {

private var tapHandlersByName: [String: [(() -> Void)]] = [:]

/**
Adds a custom block to be executed wjhen a tap is detected on a subregion
of the **attributed** text that contains the attribute named accordingly.
*/
public func addTapHandler(_ handler: @escaping(() -> Void), forAttribute attributeName: String) {
var handlers = tapHandlersByName[attributeName] ?? []
handlers.append(handler)
tapHandlersByName[attributeName] = handlers
}

// MARK: - Initialization

override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonSetup()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

override func awakeFromNib() {
super.awakeFromNib()
commonSetup()
}

private func commonSetup() {
self.delaysContentTouches = false
self.isScrollEnabled = false
self.isEditable = false
self.isUserInteractionEnabled = true
}

// MARK: - UIView

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let attributeName = self.attributeName(at: point), let handlers = tapHandlersByName[attributeName], handlers.count > 0 else {
return nil // Ignore touch
}
return self // Claim touch
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)

// find attribute name
guard let touch = touches.first, let attributeName = self.attributeName(at: touch.location(in: self)) else {
return
}

// Execute all handlers for that attribute, once:
tapHandlersByName[attributeName]?.forEach({ (handler) in
handler()
})
}

// MARK: - Internal Support

private func attributeName(at point: CGPoint) -> String? {
let location = CGPoint(
x: point.x - self.textContainerInset.left,
y: point.y - self.textContainerInset.top)

let characterIndex = self.layoutManager.characterIndex(
for: location,
in: self.textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)

guard characterIndex < self.textStorage.length else {
return nil
}

let firstAttributeName = tapHandlersByName.allKeys.first { (attributeName) -> Bool in
if self.textStorage.attribute(NSAttributedStringKey(rawValue: attributeName), at: characterIndex, effectiveRange: nil) != nil {
return true
}
return false
}
return firstAttributeName
}
}

As ususal, I'll wait a couple of days before accepting my own answer, just in case something better shows up...

Forward touches other than double tap to UITextView

This could be accomplished by:

  1. Letting UITextView handle the touch events as suggested in How to ignore touch events and pass them to another subview's UIControl objects?
  2. In the hitTest of UIView save the reference to the view in which it occurred so that it could be used later
  3. Add a custom double tap gesture recognizer, that checks if the hitTest reference is not null, indicating that the double tap happened on the target view and forward the event to that view. If not, fail the gesture recognizer. To fail the gesture recognizer you need to include UIKit/UIGestureRecognizerSubclass.h and set the state property to UIGestureRecognizerStateFailed
  4. Add the gesture recognizer to the UITextView
  5. Extend the UITextView to override the gestureRecognizers getter and loop through the gesture recognizers to require the custom gesture to fail. This has to be done in the getter as the gesturerecognizers seem to get reset often

how to add double tap gesture to UITextView

I have the solution on iOS6, we can use UIGestureRecognizerDelegate, and override gestureRecognizerShouldBegin: and gestureRecognizer:shouldReceiveTouch:. In this two method, we can check if the gesture is doubleTapGestureForZooming, if not return NO, or return YES. This works perfect in iOS6, but in iOS5 these two delegate method hasn't been called, so iOS5 may need another workaround.
Finally, I get the workaround, we can override the addGestureRecognizer method of UITextView to remove default gesture, wish this will help somebody else.

PS: we really can't remove system gestures of UITextView, we even can't change their property. It seems when event happens, all gestures of UItextview will be added again.



Related Topics



Leave a reply



Submit